From 4309b53afc908db35c053b28902629cd2d3db1db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:11:01 +0000 Subject: [PATCH 01/10] Initial plan From 6933f4d4b8cc08690656a1a80e807fa6ead32413 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:26:33 +0000 Subject: [PATCH 02/10] Add GetErrors() via CAN SDO to list device error logs Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/1545f2f0-15fc-43ef-a38c-582f6cd33483 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- esp32-web-interface.ino | 7 ++++ src/oi_can.cpp | 81 +++++++++++++++++++++++++++++++++++++++++ src/oi_can.h | 1 + 3 files changed, 89 insertions(+) diff --git a/esp32-web-interface.ino b/esp32-web-interface.ino index 2f67e3c..8516b2b 100644 --- a/esp32-web-interface.ino +++ b/esp32-web-interface.ino @@ -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"); } diff --git a/src/oi_can.cpp b/src/oi_can.cpp index 73f0450..e1a9303 100644 --- a/src/oi_can.cpp +++ b/src/oi_can.cpp @@ -51,6 +51,8 @@ #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 @@ -673,6 +675,85 @@ 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() { + if (state != IDLE) return ""; + + twai_message_t rxframe; + String result; + + JsonDocument doc; + JsonDocument filter; + + File file = SPIFFS.open(jsonFileName, "r"); + filter["lasterr"]["unit"] = true; + deserializeJson(doc, file, DeserializationOption::Filter(filter)); + file.close(); + + String unitStr; + if (!doc["lasterr"]["unit"].isNull()) + unitStr = doc["lasterr"]["unit"].as(); + + for (uint8_t index = 0; index < 100; 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 = *(uint32_t*)&rxframe.data[4]; + + 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 = *(uint32_t*)&rxframe.data[4]; + String errorName = lookupEnum(unitStr, errorNum); + + result += "[" + String(errorTime) + "]: " + errorName + "\r\n"; + } + + return result; +} + int GetNodeId() { return _nodeId; } diff --git a/src/oi_can.h b/src/oi_can.h index 9036a12..5bbecad 100644 --- a/src/oi_can.h +++ b/src/oi_can.h @@ -36,6 +36,7 @@ double GetValue(String name); bool StartStop(int opmode); bool SaveToFlash(); String StreamValues(String names, int samples); +String GetErrors(); int StartUpdate(String fileName); int GetCurrentUpdatePage(); int GetNodeId(); From fbbbd301a8f6a07fd8ce3df2a1a06b36a1c50a6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:28:51 +0000 Subject: [PATCH 03/10] Address code review: named constant, file open validation, comment Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/1545f2f0-15fc-43ef-a38c-582f6cd33483 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- src/oi_can.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/oi_can.cpp b/src/oi_can.cpp index e1a9303..015e5de 100644 --- a/src/oi_can.cpp +++ b/src/oi_can.cpp @@ -59,6 +59,7 @@ #define SDO_CMD_DEFAULTS 3 #define SDO_CMD_START 4 #define SDO_CMD_STOP 5 +#define MAX_ERROR_LOG_ENTRIES 100 namespace OICan { @@ -706,6 +707,7 @@ static String lookupEnum(const String& unitStr, uint32_t value) { } String GetErrors() { + // Returns empty string when not IDLE (e.g. busy with firmware update) or when no errors are logged if (state != IDLE) return ""; twai_message_t rxframe; @@ -715,15 +717,17 @@ String GetErrors() { JsonDocument filter; File file = SPIFFS.open(jsonFileName, "r"); - filter["lasterr"]["unit"] = true; - deserializeJson(doc, file, DeserializationOption::Filter(filter)); - file.close(); + 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(); - for (uint8_t index = 0; index < 100; index++) { + 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) From e7ae8bc3f73581acc3b273ea80f09d08a3353f1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:30:37 +0000 Subject: [PATCH 04/10] Use memcpy for safe uint32_t extraction; fix comment wording Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/1545f2f0-15fc-43ef-a38c-582f6cd33483 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- src/oi_can.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/oi_can.cpp b/src/oi_can.cpp index 015e5de..b3a36d7 100644 --- a/src/oi_can.cpp +++ b/src/oi_can.cpp @@ -707,7 +707,7 @@ static String lookupEnum(const String& unitStr, uint32_t value) { } String GetErrors() { - // Returns empty string when not IDLE (e.g. busy with firmware update) or when no errors are logged + // 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; @@ -736,7 +736,8 @@ String GetErrors() { if (rxframe.data[0] == SDO_ABORT) break; - uint32_t errorTime = *(uint32_t*)&rxframe.data[4]; + uint32_t errorTime; + memcpy(&errorTime, &rxframe.data[4], sizeof(errorTime)); if (errorTime == 0) break; @@ -749,7 +750,8 @@ String GetErrors() { if (rxframe.data[0] == SDO_ABORT) break; - uint32_t errorNum = *(uint32_t*)&rxframe.data[4]; + uint32_t errorNum; + memcpy(&errorNum, &rxframe.data[4], sizeof(errorNum)); String errorName = lookupEnum(unitStr, errorNum); result += "[" + String(errorTime) + "]: " + errorName + "\r\n"; From 9afde0f75312d6bf73d987a87b64064c9ccc7a6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:23:28 +0000 Subject: [PATCH 05/10] Call refreshMessagesBox in onLoad/refresh; make refreshStatusBox tolerant of missing items Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/c34bb97f-e26e-4dec-8a04-ad6680fc25f3 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- data/ui.js | 98 +++++++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/data/ui.js b/data/ui.js index 34a31ab..979d546 100644 --- a/data/ui.js +++ b/data/ui.js @@ -142,6 +142,7 @@ var ui = { settings.populateSettingsTab(); ui.populateFileList(); ui.refreshStatusBox(); + ui.refreshMessagesBox(); ui.getNodeId(); ui.setAutoReload(true); }, @@ -151,6 +152,7 @@ var ui = { { ui.updateTables(); ui.refreshStatusBox(); + ui.refreshMessagesBox(); }, getNodeId: function() { @@ -414,11 +416,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'); @@ -429,51 +426,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); From 4729754e2f33686545eabecd8010caabd76c8918 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:27:16 +0000 Subject: [PATCH 06/10] Reload CAN mappings after node ID/speed change Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/49fb3736-e953-464a-a987-4b2925a6c271 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- data/ui.js | 1 + 1 file changed, 1 insertion(+) diff --git a/data/ui.js b/data/ui.js index 979d546..87fd5ca 100644 --- a/data/ui.js +++ b/data/ui.js @@ -175,6 +175,7 @@ var ui = { { document.getElementById("nodeid").value = this.responseText.split(',')[0]; document.getElementById("canspeed").value = this.responseText.split(',')[1]; + inverter.canMapping(ui.populateExistingCanMappingTable); } xmlhttp.open("GET", "/nodeid?id=" + document.getElementById("nodeid").value + "&canspeed=" + document.getElementById("canspeed").value, true); From 285eb47f20427378c55c3fb2ad50a9c7c1ec8d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:32:33 +0000 Subject: [PATCH 07/10] Wait for param list before populating CAN mapping table on node ID change Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/8a2ea810-6961-4ce7-8cae-b438e49f8224 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- data/ui.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/ui.js b/data/ui.js index 87fd5ca..f726428 100644 --- a/data/ui.js +++ b/data/ui.js @@ -175,7 +175,9 @@ var ui = { { document.getElementById("nodeid").value = this.responseText.split(',')[0]; document.getElementById("canspeed").value = this.responseText.split(',')[1]; - inverter.canMapping(ui.populateExistingCanMappingTable); + inverter.getParamList(function() { + inverter.canMapping(ui.populateExistingCanMappingTable); + }); } xmlhttp.open("GET", "/nodeid?id=" + document.getElementById("nodeid").value + "&canspeed=" + document.getElementById("canspeed").value, true); From e9c539d0ac6e97122b0edfc23cbc6ac01a324de4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:53:55 +0000 Subject: [PATCH 08/10] Stop auto-refresh on param field focus; replace param-update modal with green success bar Agent-Logs-Url: https://github.com/jsphuebner/esp32-web-interface/sessions/f32a0718-29ce-43a8-a355-bd4523139f83 Co-authored-by: jsphuebner <3882041+jsphuebner@users.noreply.github.com> --- data/index.html | 4 ++++ data/style.css | 19 +++++++++++++++++++ data/ui.js | 40 ++++++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/data/index.html b/data/index.html index 06002ff..5f29818 100644 --- a/data/index.html +++ b/data/index.html @@ -63,6 +63,10 @@

Communication problem between ESP and STM

+
+

+
+