-
-
- Beta features
+
+
+
refreshing...
@@ -256,7 +258,7 @@
OpenInverter Board Firmware
To install a new firmware on your OpenInverter board click on the 'Install firmware from file' button on the right. Use the stm32_sine.bin file to install the sine firmware or use the stm32_foc.bin file to install the FOC firmware. You can find information about the latest available releases, including download links, on the OpenInverter forum here .
Web Interface Firmware
-
The ESP8266 firmware can be upgraded with platformio via OTA i.e. directly via wifi without programming cable, see forum .
+
The ESP32 firmware can be upgraded with platformio via OTA i.e. directly via wifi without programming cable, see forum .
You can also program with a cable, see here for more details.
Web Interface Application
@@ -493,9 +495,10 @@
Existing CAN Mappings
Spot Value
Transmit or Receive
ID
-
Offset
+
Position
Length
Gain
+
Offset
Delete Mapping
diff --git a/data/inverter.js b/data/inverter.js
index 521eaaf..79fa8c3 100644
--- a/data/inverter.js
+++ b/data/inverter.js
@@ -92,6 +92,23 @@ var inverter = {
{
if (replyFunc) replyFunc(this.responseText);
}
+ xmlhttp.onreadystatechange = function() {
+ if (xmlhttp.readyState === XMLHttpRequest.DONE) {
+ console.log(req + ": " + xmlhttp.status);
+ if (xmlhttp.status != 200) {
+ paramsCache.failedFetchCount += 1;
+ if ( paramsCache.failedFetchCount >= 2 && typeof ui !== 'undefined'){
+ ui.showCommunicationErrorBar();
+ }
+ }
+ else {
+ paramsCache.failedFetchCount = 0;
+ }
+ if ( paramsCache.failedFetchCount < 2 && typeof ui !== 'undefined') {
+ ui.hideCommunicationErrorBar();
+ }
+ }
+ }
if (repeat)
req += "&repeat=" + repeat;
@@ -107,8 +124,7 @@ var inverter = {
inverter.sendCmd(cmd, function(reply) {
var params = {};
- try
- {
+ try {
params = JSON.parse(reply);
for (var name in params)
@@ -119,19 +135,8 @@ var inverter = {
if (name == "version")
inverter.firmwareVersion = parseFloat(param.value);
}
- paramsCache.failedFetchCount = 0;
- }
- catch(ex)
- {
- paramsCache.failedFetchCount += 1;
- if ( paramsCache.failedFetchCount >= 2 ){
- ui.showCommunicationErrorBar();
- }
- }
- if ( paramsCache.failedFetchCount < 2 )
- {
- ui.hideCommunicationErrorBar();
- }
+ } catch(ex) { console.error('Failed to parse params:', ex); }
+
paramsCache.setData(params);
if (replyFunc) replyFunc(params);
});
@@ -157,10 +162,7 @@ var inverter = {
replyFunc(values);
};
- if (inverter.firmwareVersion < 3.53 || items.length > 10)
- inverter.sendCmd("get " + items.join(','), process, repeat);
- else
- inverter.sendCmd("stream " + repeat + " " + items.join(','), process);
+ inverter.sendCmd("stream " + repeat + " " + items.join(','), process);
},
diff --git a/data/style.css b/data/style.css
index 9f9f0ef..b2d9717 100644
--- a/data/style.css
+++ b/data/style.css
@@ -656,3 +656,22 @@ input:checked + .slider:before {
.communication-error-bar p {
text-align: center;
}
+
+.param-success-bar {
+ display: none;
+ padding: 15px;
+ background-color: #4caf50;
+ color: white;
+ z-index: 100;
+ margin: 0 auto;
+ position: relative;
+ width: 30%;
+ top: 0px;
+ clear: left;
+ height: 20px;
+ border-radius: 0px 0px 20px 20px;
+}
+
+.param-success-bar p {
+ text-align: center;
+}
diff --git a/data/ui.js b/data/ui.js
index 07d647a..90b1960 100644
--- a/data/ui.js
+++ b/data/ui.js
@@ -32,13 +32,15 @@ var ui = {
// Status of visibility of parameter categories. E.g. Motor, Inverter. true = visible, false = not visible.
categoryVisible: {},
+ navbarIsBig: true,
+
shrinkNavbar: function() {
- document.getElementById("navbar").style.width = "80px";
+ document.getElementById("navbar").style.width = "60px";
var cw = document.getElementById("content-wrapper");
- cw.style.left = "80px";
- cw.style.width = "calc(100% - 80px)";
+ cw.style.left = "60px";
+ cw.style.width = "calc(100% - 60px)";
var logo = document.getElementById("logo");
- logo.style.width = "80px";
+ logo.style.width = "60px";
logo.style.height = "50px";
// buttons
var buttons = document.getElementsByClassName("buttonimg");
@@ -131,10 +133,24 @@ var ui = {
}
});
+ // Pause auto-refresh while the user is editing a parameter field, resume when done
+ var paramsTable = document.getElementById('params');
+ paramsTable.addEventListener('focusin', function(event) {
+ if (event.target.tagName === 'INPUT' || event.target.tagName === 'SELECT') {
+ clearInterval(ui.autoRefreshHandle);
+ }
+ });
+ paramsTable.addEventListener('focusout', function(event) {
+ if (event.target.tagName === 'INPUT' || event.target.tagName === 'SELECT') {
+ if (document.getElementById('auto-reload-checkbox').checked) {
+ ui.autoRefreshHandle = setInterval(ui.refresh, 2000);
+ }
+ }
+ });
+
ui.updateTables();
plot.generateChart();
ui.parameterDatabaseCheckForUpdates();
- ui.populateSpotValueDropDown();
ui.populateExistingCanMappingTable();
wifi.populateWiFiTab();
ui.populateFileList();
@@ -229,7 +245,7 @@ var ui = {
if (param.enums[param.value])
{
- valInput = '
';
+ valInput = '';
for (var idx in param.enums)
{
@@ -253,8 +269,9 @@ var ui = {
}
else
{
+ var step = Number.isInteger(param.value) ? 1 : 0.04;
valInput = ' ';
+ '" step="' + step + '" value="' + param.value + '" onchange="ui.sendParameterUpdate(\'' + name + '\', this.value)"/>';
}
if (param.i !== undefined)
@@ -295,6 +312,7 @@ var ui = {
}
}
ui.populateVersion();
+ ui.populateSpotValueDropDown();
document.getElementById("paramDownload").href = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(params, null, 2));
document.getElementById("spinner-div").style.visibility = "hidden";
});
@@ -354,7 +372,7 @@ var ui = {
// run the poll function every 2 seconds
if (enable) {
autoReloadCheckbox.checked = true;
- ui.autoRefreshHandle = setInterval(ui.refresh, 3000);
+ ui.autoRefreshHandle = setInterval(ui.refresh, 2000);
}
else {
autoReloadCheckbox.checked = false;
@@ -372,6 +390,16 @@ var ui = {
document.getElementById('communication-error-bar').style.display = 'none';
},
+ /** @brief Show green success bar with a message, then auto-hide after a delay */
+ showParamSuccessBar: function(message) {
+ document.getElementById('param-success-bar-text').textContent = message;
+ document.getElementById('param-success-bar').style.display = 'block';
+ clearTimeout(ui.paramSuccessBarTimer);
+ ui.paramSuccessBarTimer = setTimeout(function() {
+ document.getElementById('param-success-bar').style.display = 'none';
+ }, 2000);
+ },
+
/**
* ~~~ DASHBOARD ~~~
*/
@@ -383,11 +411,6 @@ var ui = {
var statusDiv = document.getElementById('top-left');
var status = paramsCache.get('status');
-
- if ( status == null ){
- return;
- }
-
var lasterr = paramsCache.get('lasterr');
var udc = paramsCache.get('udc');
var tmphs = paramsCache.get('tmphs');
@@ -398,50 +421,60 @@ var ui = {
var tbl = document.createElement('table');
var tbody = document.createElement('tbody');
// status
- var tr = document.createElement('tr');
- var td = document.createElement('td');
- td.appendChild(document.createTextNode('Status'));
- tr.appendChild(td);
- td = document.createElement('td');
- td.appendChild(document.createTextNode(status));
- tr.appendChild(td);
- tbody.appendChild(tr);
+ if (status != null) {
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.appendChild(document.createTextNode('Status'));
+ tr.appendChild(td);
+ td = document.createElement('td');
+ td.appendChild(document.createTextNode(status));
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
// opmode
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.appendChild(document.createTextNode('Opmode'));
- tr.appendChild(td);
- td = document.createElement('td');
- td.appendChild(document.createTextNode(opmode));
- tr.appendChild(td);
- tbody.appendChild(tr);
+ if (opmode != null) {
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.appendChild(document.createTextNode('Opmode'));
+ tr.appendChild(td);
+ td = document.createElement('td');
+ td.appendChild(document.createTextNode(opmode));
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
// lasterr
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.appendChild(document.createTextNode('Last error'));
- tr.appendChild(td);
- td = document.createElement('td');
- td.appendChild(document.createTextNode(lasterr));
- tr.appendChild(td);
- tbody.appendChild(tr);
+ if (lasterr != null) {
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.appendChild(document.createTextNode('Last error'));
+ tr.appendChild(td);
+ td = document.createElement('td');
+ td.appendChild(document.createTextNode(lasterr));
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
// udc
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.appendChild(document.createTextNode('Battery voltage (udc)'));
- tr.appendChild(td);
- td = document.createElement('td');
- td.appendChild(document.createTextNode(udc));
- tr.appendChild(td);
- tbody.appendChild(tr);
+ if (udc != null) {
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.appendChild(document.createTextNode('Battery voltage (udc)'));
+ tr.appendChild(td);
+ td = document.createElement('td');
+ td.appendChild(document.createTextNode(udc));
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
// tmphs
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.appendChild(document.createTextNode('Inverter temperature'));
- tr.appendChild(td);
- td = document.createElement('td');
- td.appendChild(document.createTextNode(tmphs));
- tr.appendChild(td);
- tbody.appendChild(tr);
+ if (tmphs != null) {
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.appendChild(document.createTextNode('Inverter temperature'));
+ tr.appendChild(td);
+ td = document.createElement('td');
+ td.appendChild(document.createTextNode(tmphs));
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
tbl.appendChild(tbody);
@@ -562,13 +595,10 @@ var ui = {
xmlhttp.onload = function()
{
- // Show popup reporting upload completion
- modal.emptyModal('small');
- modal.appendToModal('small', 'File upload complete');
- modal.showModal('small');
// Refresh the list of files on the 'files' page
ui.populateFileList();
- setTimeout(function() { modal.hideModal('small') }, 2000);
+ // Show non-intrusive success notification
+ ui.showParamSuccessBar('File upload complete');
}
xmlhttp.open("POST", "/edit");
@@ -787,6 +817,16 @@ var ui = {
* ~~~ PARAMETERS ~~~
*/
+ /** @brief Send parameter update to inverter and show result in success bar */
+ sendParameterUpdate: function(param, value)
+ {
+ var c = 'set ' + param + ' ' + value;
+ inverter.sendCmd(c, function(reply)
+ {
+ ui.showParamSuccessBar(param + ' = ' + value + ' \u2014 ' + reply.trim());
+ });
+ },
+
/** @brief Show modal box with the result of parameter update */
showParamUpdateModal: async function(param, value)
{
@@ -1268,6 +1308,9 @@ var ui = {
// cangain
var canGainCell = tr.insertCell(-1);
canGainCell.innerHTML = param.cangain;
+ // canoffset value (value offset to add)
+ var canOffsetValCell = tr.insertCell(-1);
+ canOffsetValCell.innerHTML = typeof param.canoffset_value !== 'undefined' ? param.canoffset_value : 0;
// delete button
var canDeleteCell = tr.insertCell(-1);
var cmd = "inverter.canMapping('del', '" + name + "');ui.populateExistingCanMappingTable();";
@@ -1281,6 +1324,8 @@ var ui = {
populateSpotValueDropDown: function()
{
var select = document.getElementById("add-can-mapping-spot-value-drop-down");
+ // Clear existing options before repopulating to avoid duplicates on refresh
+ while (select.options.length > 0) select.remove(0);
inverter.getParamList(function(values) {
for (var name in values) {
var param = values[name];
diff --git a/esp32-web-interface.ino b/esp32-web-interface.ino
index 3c2c398..26767fc 100644
--- a/esp32-web-interface.ino
+++ b/esp32-web-interface.ino
@@ -243,7 +243,8 @@ bool handleFileRead(String path){
if(SPIFFS.exists(pathWithGz))
path += ".gz";
File file = SPIFFS.open(path, "r");
- size_t sent = server.streamFile(file, contentType);
+ server.sendHeader("Cache-Control", "max-age=86400");
+ server.streamFile(file, contentType);
file.close();
return true;
}
@@ -256,7 +257,7 @@ bool handleFileRead(String path){
if (SD_MMC.exists(path)) {
File file = SD_MMC.open(path, "r");
- size_t sent = server.streamFile(file, contentType);
+ server.streamFile(file, contentType);
file.close();
return true;
}
@@ -796,7 +797,6 @@ void setup(void){
});
server.begin();
- server.client().setNoDelay(1);
MDNS.addService("http", "tcp", 80);
}
@@ -821,7 +821,7 @@ void binaryLoggingStart()
uart_set_baudrate(INVERTER_PORT, 2250000);
uart_write_bytes(INVERTER_PORT, "\n", 1);
delay(1);
- uart_write_bytes(INVERTER_PORT, "binarylogging 0", strnlen("binarylogging 0", UART_MESSBUF_SIZE));
+ uart_write_bytes(INVERTER_PORT, "binarylogging 0", strlen("binarylogging 0"));
uart_write_bytes(INVERTER_PORT, "\n", 1);
uart_wait_tx_done(INVERTER_PORT, UART_TIMEOUT);
uart_set_baudrate(INVERTER_PORT, 115200);
@@ -835,7 +835,7 @@ void binaryLoggingStop()
{
uart_write_bytes(INVERTER_PORT, "\n", 1);
delay(1);
- uart_write_bytes(INVERTER_PORT, "binarylogging 0", strnlen("binarylogging 0", UART_MESSBUF_SIZE));
+ uart_write_bytes(INVERTER_PORT, "binarylogging 0", strlen("binarylogging 0"));
uart_write_bytes(INVERTER_PORT, "\n", 1);
uart_wait_tx_done(INVERTER_PORT, UART_TIMEOUT);
uart_set_baudrate(INVERTER_PORT, 115200);