Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions data/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ <h2 id="large-modal-header"></h2>
<p>Communication problem between ESP and STM</p>
</div>

<div id="param-success-bar" class="param-success-bar">
<p id="param-success-bar-text"></p>
</div>

<!-- 'Add CAN Mapping' modal -->
<div id="can-mapping-modal-overlay" class="modal-overlay">
<div id="can-mapping-modal-container" class="can-mapping-modal-container">
Expand Down
19 changes: 19 additions & 0 deletions data/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
149 changes: 88 additions & 61 deletions data/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ 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();
Expand All @@ -142,6 +157,7 @@ var ui = {
settings.populateSettingsTab();
ui.populateFileList();
ui.refreshStatusBox();
ui.refreshMessagesBox();
ui.getNodeId();
ui.setAutoReload(true);
},
Expand All @@ -151,6 +167,7 @@ var ui = {
{
ui.updateTables();
ui.refreshStatusBox();
ui.refreshMessagesBox();
},

getNodeId: function() {
Expand All @@ -173,6 +190,9 @@ var ui = {
{
document.getElementById("nodeid").value = this.responseText.split(',')[0];
document.getElementById("canspeed").value = this.responseText.split(',')[1];
inverter.getParamList(function() {
inverter.canMapping(ui.populateExistingCanMappingTable);
});
}

xmlhttp.open("GET", "/nodeid?id=" + document.getElementById("nodeid").value + "&canspeed=" + document.getElementById("canspeed").value, true);
Expand Down Expand Up @@ -257,7 +277,7 @@ var ui = {
if (param.enums[param.value])
{

valInput = '<SELECT onchange="ui.showParamUpdateModal(\'' + name + '\', this.value)">';
valInput = '<SELECT onchange="ui.sendParameterUpdate(\'' + name + '\', this.value)">';

for (var idx in param.enums)
{
Expand All @@ -281,8 +301,9 @@ var ui = {
}
else
{
var step = Number.isInteger(param.value) ? 1 : 0.04;
valInput = '<INPUT type="number" min="' + param.minimum + '" max="' + param.maximum +
'" step="0.05" value="' + param.value + '" onchange="ui.showParamUpdateModal(\'' + name + '\', this.value)"/>';
'" step="' + step + '" value="' + param.value + '" onchange="ui.sendParameterUpdate(\'' + name + '\', this.value)"/>';
}

if (param.i !== undefined)
Expand Down Expand Up @@ -403,6 +424,16 @@ var ui = {
hideCommunicationErrorBar: function() {
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 ~~~
*/
Expand All @@ -414,11 +445,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');
Expand All @@ -429,51 +455,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);
statusDiv.appendChild(tbl);
Expand Down Expand Up @@ -590,13 +625,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");
Expand Down Expand Up @@ -815,19 +847,14 @@ var ui = {
* ~~~ PARAMETERS ~~~
*/

/** @brief Show modal box with the result of parameter update */
showParamUpdateModal: async function(param, value)
/** @brief Send parameter update to inverter and show result in success bar */
sendParameterUpdate: function(param, value)
{
var c = 'set ' + param + ' ' + value;
modal.emptyModal('small');
modal.showModal('small');
modal.appendToModal('small', 'Setting ' + param + ' to ' + value + "<br>");
inverter.sendCmd(c, function(reply)
{
modal.appendToModal('small', reply);
ui.showParamSuccessBar(param + ' = ' + value + ' \u2014 ' + reply.trim());
});
await sleep(2000);
modal.hideModal('small');
},

/** @brief Show confirmation that params have been saved */
Expand Down
7 changes: 7 additions & 0 deletions esp32-web-interface.ino
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,13 @@ static void handleCommand() {
server.send(200, "text/plain", "Start command failed");
}
}
else if (cmd == "errors") {
String errors = OICan::GetErrors();
if (errors.length() == 0)
server.send(200, "text/plain", "No errors");
else
server.send(200, "text/plain", errors);
}
else {
server.send(200, "text/plain", "Unknown command");
}
Expand Down
87 changes: 87 additions & 0 deletions src/oi_can.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@
#define SDO_INDEX_SERIAL 0x5000
#define SDO_INDEX_STRINGS 0x5001
#define SDO_INDEX_COMMANDS 0x5002
#define SDO_INDEX_ERROR_NUM 0x5003
#define SDO_INDEX_ERROR_TIME 0x5004
#define SDO_CMD_SAVE 0
#define SDO_CMD_LOAD 1
#define SDO_CMD_RESET 2
#define SDO_CMD_DEFAULTS 3
#define SDO_CMD_START 4
#define SDO_CMD_STOP 5
#define MAX_ERROR_LOG_ENTRIES 100

namespace OICan {

Expand Down Expand Up @@ -673,6 +676,90 @@ double GetValue(String name) {
}
}

static String lookupEnum(const String& unitStr, uint32_t value) {
String searchKey = String(value) + "=";
int pos = -1;

if (unitStr.startsWith(searchKey)) {
pos = searchKey.length();
} else {
// Search for ",N=" or ", N=" pattern to avoid partial matches
int i = unitStr.indexOf("," + searchKey);
if (i >= 0) {
pos = i + 1 + searchKey.length();
} else {
i = unitStr.indexOf(", " + searchKey);
if (i >= 0)
pos = i + 2 + searchKey.length();
}
}

if (pos < 0)
return String(value);

int end = unitStr.indexOf(',', pos);
if (end < 0)
end = unitStr.length();

String name = unitStr.substring(pos, end);
name.trim();
return name.length() > 0 ? name : String(value);
}

String GetErrors() {
// Returns empty string when not IDLE (e.g. busy with firmware update) or when the device reports no errors
if (state != IDLE) return "";

twai_message_t rxframe;
String result;

JsonDocument doc;
JsonDocument filter;

File file = SPIFFS.open(jsonFileName, "r");
if (file) {
filter["lasterr"]["unit"] = true;
deserializeJson(doc, file, DeserializationOption::Filter(filter));
file.close();
}

String unitStr;
if (!doc["lasterr"]["unit"].isNull())
unitStr = doc["lasterr"]["unit"].as<String>();

for (uint8_t index = 0; index < MAX_ERROR_LOG_ENTRIES; index++) {
requestSdoElement(SDO_INDEX_ERROR_TIME, index);

if (twai_receive(&rxframe, pdMS_TO_TICKS(10)) != ESP_OK)
break;

if (rxframe.data[0] == SDO_ABORT)
break;

uint32_t errorTime;
memcpy(&errorTime, &rxframe.data[4], sizeof(errorTime));

if (errorTime == 0)
break;

requestSdoElement(SDO_INDEX_ERROR_NUM, index);

if (twai_receive(&rxframe, pdMS_TO_TICKS(10)) != ESP_OK)
break;

if (rxframe.data[0] == SDO_ABORT)
break;

uint32_t errorNum;
memcpy(&errorNum, &rxframe.data[4], sizeof(errorNum));
String errorName = lookupEnum(unitStr, errorNum);

result += "[" + String(errorTime) + "]: " + errorName + "\r\n";
}

return result;
}

int GetNodeId() {
return _nodeId;
}
Expand Down
Loading