From 8393f24ae231a5aad456b450c0a498476db0fe7d Mon Sep 17 00:00:00 2001 From: Marcel Peterkau Date: Tue, 12 Aug 2025 14:21:53 +0200 Subject: [PATCH] improved WebUI --- Software/data_src/index.htm | 34 +-- Software/data_src/static/js/websocket.js | 186 +++++++++---- Software/src/webui.cpp | 326 ++++++++++++++++------- 3 files changed, 382 insertions(+), 164 deletions(-) diff --git a/Software/data_src/index.htm b/Software/data_src/index.htm index 8f6e93f..b0c2483 100644 --- a/Software/data_src/index.htm +++ b/Software/data_src/index.htm @@ -8,11 +8,11 @@ - - - - - + + + + + @@ -216,10 +216,7 @@
-
@@ -298,8 +295,6 @@
@@ -510,21 +505,26 @@ - + - + - - + + + + + + - + +
Parameter - Value + ParameterValue
Firmware Version%SW_VERSION%
Flash Version%FS_VERSION%Benötigte Flash Version
Installierte Flash Version
Git Revision%GIT_REV%

diff --git a/Software/data_src/static/js/websocket.js b/Software/data_src/static/js/websocket.js index fa3b7a6..842d8e1 100644 --- a/Software/data_src/static/js/websocket.js +++ b/Software/data_src/static/js/websocket.js @@ -84,7 +84,6 @@ function onMessage(event) { processDTCNotifications(dtcArray); fillDTCTable(dtcArray); - } else if (data.startsWith("MAPPING_STATUS:")) { const data_sliced = data.slice(15); statusMapping = createMapping(data_sliced); @@ -100,19 +99,15 @@ function onMessage(event) { const data_sliced = data.slice(7); const result = processDataString(data_sliced, staticMapping); fillValuesToHTML(result); - console.log(result); overlay.style.display = "none"; } } function createMapping(mappingString) { - const mappingArray = mappingString.split(";"); - const mapping = []; - - mappingArray.forEach((variable) => { - if (variable !== null) mapping.push(variable.trim()); - }); - return mapping; + return mappingString + .split(";") + .map((s) => s.trim()) + .filter((s) => s !== ""); } function processDataString(dataString, mapping) { @@ -157,43 +152,136 @@ function do_resize(textbox) { else textbox.rows = rows; } -function fillValuesToHTML(dataset) { - for (var key in dataset) { - var key_prefixed = "data-" + key; - var elements = document.getElementsByClassName(key_prefixed); +// --- Globale Puffer für Select-Handling --- +const selectDesiredValue = Object.create(null); // keyBase -> desired value ("speedsource" -> "GPS") +const selectOptionsReady = Object.create(null); // keyBase -> true/false - if (elements.length > 0) { - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; +function splitCsv(s) { + return (s || "") + .split(",") + .map((x) => x.trim()) + .filter(Boolean); +} - if (element.type === "checkbox") { - // Wenn das Element ein Kontrollkästchen ist - element.checked = dataset[key] == 1 ? true : false; - } else if (element.tagName === "SELECT") { - // Wenn das Element ein Dropdown ist - setDropdownValue(element, dataset[key]); - } else if (element.classList.contains("progress-bar")) { - // Wenn das Element eine Fortschrittsleiste ist - updateProgressBar(element, dataset[key]); - } else if (element.classList.contains("hideable")) { - // Wenn das Element ein Settingsabschnitt-div ist - if (dataset[key] == 0) element.style.display = "none"; - else element.style.display = ""; - } else { - // Standardmäßig für Textfelder und andere Elemente - element.value = dataset[key]; - } - } - } +function hasOption(selectEl, value) { + for (let i = 0; i < selectEl.options.length; i++) { + if (selectEl.options[i].value === value) return true; + } + return false; +} + +function addOption(selectEl, value, selectIt = false) { + const o = document.createElement("option"); + o.value = value; + o.textContent = value; + selectEl.appendChild(o); + if (selectIt) { + selectEl.value = value; } } -// Funktion zum Setzen des ausgewählten Werts für Dropdowns +function populateSelect(selectId, options, currentValue) { + const sel = document.getElementById(selectId); + if (!sel) return; + + // 1) komplett ersetzen + sel.innerHTML = ""; + options.forEach((opt) => addOption(sel, opt, false)); + + // 2) Zielwert bestimmen (currentValue > bereits gepuffert > vorhandener Wert > erste Option) + const wanted = + (currentValue && currentValue.length ? currentValue : null) ?? + selectDesiredValue[selectId] ?? + sel.value ?? + (options[0] || ""); + + // 3) Falls gewünschte Option fehlt, hinzufügen + if (wanted && !hasOption(sel, wanted)) addOption(sel, wanted, false); + + // 4) Setzen + sel.value = wanted; + + // 5) Markieren: Optionen sind ready + selectOptionsReady[selectId] = true; + // Verbrauchte Wunschwerte löschen + delete selectDesiredValue[selectId]; +} + +// Robust: setzt den Wert; wenn Option fehlt, füge sie hinzu function setDropdownValue(selectElement, value) { - for (var i = 0; i < selectElement.options.length; i++) { - if (selectElement.options[i].value === value) { - selectElement.selectedIndex = i; - break; + if (!value) return; + if (!hasOption(selectElement, value)) { + addOption(selectElement, value, false); + } + selectElement.value = value; +} + +// Core: schreibt Werte in DOM, erkennt *-options und füllt Selects +function fillValuesToHTML(dataset) { + for (const key in dataset) { + const val = dataset[key]; + + // A) Optionen-Feld? (z.B. "speedsource-options") + if (key.endsWith("-options")) { + const base = key.slice(0, -"-options".length); // "speedsource" + const selectId = base; // ID = keyBase + populateSelect( + selectId, + splitCsv(val), + selectDesiredValue[selectId] || undefined + ); + continue; // fertig mit diesem key + } + + // B) Normales Feld + const key_prefixed = "data-" + key; + const elements = document.getElementsByClassName(key_prefixed); + if (elements.length === 0) continue; + + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + + // Checkbox? + if (el.type === "checkbox") { + el.checked = val == 1; + continue; + } + + // Select? + if (el.tagName === "SELECT") { + const selectId = el.id || key; // ID hat Vorrang, sonst key + // Wenn die Optionen für dieses Select noch NICHT bereit sind, Wunschwert puffern + if (!selectOptionsReady[selectId]) { + selectDesiredValue[selectId] = val; + // Sicherheitsnetz: falls doch schon Optionen existieren, sofort setzen + setDropdownValue(el, val); + } else { + // Optionen sind ready -> ganz normal setzen (ggf. Option ergänzen) + setDropdownValue(el, val); + } + continue; + } + + // Progress-Bar? + if (el.classList.contains("progress-bar")) { + updateProgressBar(el, val); + continue; + } + + // Hideable-Section? + if (el.classList.contains("hideable")) { + el.style.display = val == 0 ? "none" : ""; + continue; + } + + // Input/Textarea? + if ("value" in el) { + el.value = val; + continue; + } + + // Fallback: content nodes (td, span, div) + el.textContent = val; } } } @@ -212,15 +300,15 @@ function showNotification(message, type) { // Erstellen Sie ein Bootstrap-Alert-Element var alertElement = $( '" + type + + ' alert-dismissible fade show notification" role="alert">' + + "" + + message + + "" + + '" + + "" ); // Fügen Sie das Alert-Element dem Container hinzu diff --git a/Software/src/webui.cpp b/Software/src/webui.cpp index f1f4b59..f205739 100644 --- a/Software/src/webui.cpp +++ b/Software/src/webui.cpp @@ -12,6 +12,10 @@ */ #include "webui.h" +#include "common.h" +#include // std::unique_ptr +#include // strlen, strncpy, memcpy +#include // std::clamp AsyncWebServer webServer(80); @@ -19,6 +23,28 @@ const char *PARAM_MESSAGE = "message"; SpeedSource_t speedsourcePreselect; /**< Preselect Memory for change SourceAdress */ +struct WsInitBurst +{ + uint32_t client_id = 0; + uint8_t step = 0; // 0..N + uint32_t next_due_ms = 0; + bool active = false; +} g_wsBurst; + +static const char kMappingStatus[] = + "MAPPING_STATUS:systemstatus;tankremain;odometer;"; + +static const char kMappingStatic[] = + "MAPPING_STATIC:" + "lubedistancenormal;lubedistancerain;washdistance;washinterval;" + "tankcap;pumppulse;tankwarn;pulserev;tirewidth;tireratio;tiredia;" + "speedsource;speedsource-options;" + "gpsbaud;gpsbaud-options;" + "cansource;cansource-options;" + "ledmodeflash;ledmaxbrightness;ledminbrightness;" + "showimpulse;showgps;showcan;bleedingpulses;wifi-ssid;wifi-password;" + "fw-version;req-flash-version;git-rev;flash-version"; + String processor(const String &var); void WebserverNotFound_Callback(AsyncWebServerRequest *request); void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); @@ -38,6 +64,34 @@ void Websocket_HandleSettings(uint8_t *data); void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierBufferSize, char *valueBuffer, size_t valueBufferSize); int findIndexByString(const char *searchString, const char *const *array, int arraySize); +// ---------- small helpers (safety) ---------- +static inline const char *nz(const char *p) { return p ? p : ""; } + +static inline String tableStr(const char *const *tbl, int idx, int size) +{ + if (idx < 0 || idx >= size) + return String(); + const char *p = tbl[idx]; + return p ? String(p) : String(); +} + +static void appendCsv(String &out, const char *const *arr, size_t n) +{ + for (size_t i = 0; i < n; ++i) + { + if (!arr[i]) + continue; + out += arr[i]; + if (i + 1 < n) + out += ","; + } +} + +static inline bool validIndex(int idx, int size) +{ + return idx >= 0 && idx < size; +} + /** * @brief Initializes the web-based user interface (WebUI) for the ChainLube application. * @@ -116,6 +170,54 @@ void Webserver_Process() Websocket_RefreshClientData_Status(0); previousMillis = millis(); } + + // Gestaffelter Initial-Burst nach Verbindungsaufbau + if (g_wsBurst.active && millis() >= g_wsBurst.next_due_ms) + { + if (!webSocket.hasClient(g_wsBurst.client_id)) + { + g_wsBurst.active = false; // Client ist schon wieder weg + } + else if (!webSocket.availableForWrite(g_wsBurst.client_id)) + { + g_wsBurst.next_due_ms = millis() + 10; // kurz warten, Puffer voll + } + else + { + switch (g_wsBurst.step) + { + case 0: + // Mapping Status + webSocket.text(g_wsBurst.client_id, kMappingStatus); + g_wsBurst.step++; + g_wsBurst.next_due_ms = millis() + 25; + break; + + case 1: + // Status-Daten (send_mapping=false!) + Websocket_RefreshClientData_Status(g_wsBurst.client_id, false); + g_wsBurst.step++; + g_wsBurst.next_due_ms = millis() + 25; + break; + + case 2: + // Mapping Static (achte auf 'wifi-password') + webSocket.text(g_wsBurst.client_id, kMappingStatic); + g_wsBurst.step++; + g_wsBurst.next_due_ms = millis() + 25; + break; + + case 3: + // Static-Daten (send_mapping=false!) + Websocket_RefreshClientData_Static(g_wsBurst.client_id, false); + g_wsBurst.step++; + [[fallthrough]]; + default: + g_wsBurst.active = false; + break; + } + } + } } /** @@ -165,14 +267,15 @@ void GetFlashVersion(char *buff, size_t buff_size) { File this_file = LittleFS.open("version", "r"); if (!this_file) - { // failed to open the file, retrn empty result + { // failed to open the file, return empty result buff[0] = '\0'; return; } if (this_file.available()) { - int bytes_read; - bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1); + int bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1); + if (bytes_read < 0) + bytes_read = 0; buff[bytes_read] = '\0'; } this_file.close(); @@ -270,7 +373,7 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f } } - if (buffer != NULL) + if (buffer != NULL && len > 0) { memcpy(buffer + read_ptr, data, len); read_ptr = read_ptr + len; @@ -280,6 +383,11 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f { if (buffer != NULL) { + // Ensure zero-termination just in case + if (read_ptr >= 1536) + read_ptr = 1535; + buffer[read_ptr] = '\0'; + Serial.print(buffer); JsonDocument json; error = deserializeJson(json, buffer); @@ -323,7 +431,11 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f } } - free(buffer); + if (buffer) + { + free(buffer); + buffer = NULL; + } AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); response->addHeader("Refresh", "20"); @@ -354,10 +466,10 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request) char buffer[16]; - info["DeviceName"] = globals.DeviceName; + info["DeviceName"] = nz(globals.DeviceName); sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); info["FW-Version"] = buffer; - info["FS-Version"] = globals.FlashVersion; + info["FS-Version"] = nz(globals.FlashVersion); snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); info["Git-Hash"] = buffer; @@ -395,11 +507,16 @@ void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *clien switch (type) { case WS_EVT_CONNECT: - Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); - Websocket_RefreshClientData_Status(client->id(), true); - Websocket_RefreshClientData_Static(client->id(), true); - Websocket_RefreshClientData_DTCs(client->id()); + { + Debug_pushMessage("WebSocket client #%u connected from %s\n", + client->id(), client->remoteIP().toString().c_str()); + // NICHT direkt senden! Nur Burst triggern: + g_wsBurst.client_id = client->id(); + g_wsBurst.step = 0; + g_wsBurst.next_due_ms = millis(); // sofort startbereit + g_wsBurst.active = true; break; + } case WS_EVT_DISCONNECT: Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); break; @@ -427,20 +544,24 @@ void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) AwsFrameInfo *info = (AwsFrameInfo *)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { - data[len] = 0; - Debug_pushMessage("Websocket-Message (len: %d): %s\n", len, (char *)data); + // Create a safe, null-terminated local copy + std::unique_ptr buf(new char[len + 1]); + memcpy(buf.get(), data, len); + buf[len] = '\0'; - if (strncmp((char *)data, "btn-", strlen("btn-")) == 0) + Debug_pushMessage("Websocket-Message (len: %d): %s\n", (int)len, buf.get()); + + if (strncmp(buf.get(), "btn-", 4) == 0) { - Websocket_HandleButtons(data + strlen("btn-")); + Websocket_HandleButtons((uint8_t *)buf.get() + 4); } - else if (strncmp((char *)data, "set-", strlen("set-")) == 0) + else if (strncmp(buf.get(), "set-", 4) == 0) { - Websocket_HandleSettings(data + strlen("set-")); + Websocket_HandleSettings((uint8_t *)buf.get() + 4); } else { - Debug_pushMessage("Got unknown Websocket-Message '%s' from client\n", (char *)data); + Debug_pushMessage("Got unknown Websocket-Message '%s' from client\n", buf.get()); } } } @@ -527,18 +648,27 @@ void Websocket_HandleSettings(uint8_t *data) } else if (strcmp(identifier, "speedsource") == 0) { - int index = findIndexByString(value, SpeedSourceString, SpeedSourceString_Elements); - speedsourcePreselect = (SpeedSource_t)index; + int index = findIndexByString(value, SpeedSourceString, (int)SpeedSourceString_Elements); + if (validIndex(index, (int)SpeedSourceString_Elements)) + speedsourcePreselect = (SpeedSource_t)index; + else + Debug_pushMessage("Invalid speedsource '%s'\n", value); } else if (strcmp(identifier, "cansource") == 0) { - int index = findIndexByString(value, CANSourceString, CANSourceString_Elements); - LubeConfig.CANSource = (CANSource_t)index; + int index = findIndexByString(value, CANSourceString, (int)CANSourceString_Elements); + if (validIndex(index, (int)CANSourceString_Elements)) + LubeConfig.CANSource = (CANSource_t)index; + else + Debug_pushMessage("Invalid cansource '%s'\n", value); } else if (strcmp(identifier, "gpsbaud") == 0) { - int index = findIndexByString(value, GPSBaudRateString, GPSBaudRateString_Elements); - LubeConfig.GPSBaudRate = (GPSBaudRate_t)index; + int index = findIndexByString(value, GPSBaudRateString, (int)GPSBaudRateString_Elements); + if (validIndex(index, (int)GPSBaudRateString_Elements)) + LubeConfig.GPSBaudRate = (GPSBaudRate_t)index; + else + Debug_pushMessage("Invalid gpsbaud '%s'\n", value); } else if (strcmp(identifier, "ledmaxbrightness") == 0) { @@ -570,7 +700,7 @@ void Websocket_HandleSettings(uint8_t *data) } else if (strcmp(identifier, "ledmodeflash") == 0) { - LubeConfig.LED_Mode_Flash = value[0] == '1' ? true : false; + LubeConfig.LED_Mode_Flash = value[0] == '1'; } else if (strcmp(identifier, "wifi-ssid") == 0) { @@ -663,21 +793,22 @@ void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping) if (send_mapping) { - const char mapping[] = "MAPPING_STATUS:" - "systemstatus;" - "tankremain;" - "odometer;"; - if (client_id > 0) - webSocket.text(client_id, mapping); + webSocket.text(client_id, kMappingStatus); else - webSocket.textAll(mapping); + webSocket.textAll(kMappingStatus); } String temp = "STATUS:"; - temp.concat(String(globals.systemStatustxt) + ";"); - temp.concat(String((PersistenceData.tankRemain_microL / 10) / LubeConfig.tankCapacity_ml) + ";"); + temp.concat(String(nz(globals.systemStatustxt)) + ";"); + + // Guard against division by zero (capacity==0) + uint32_t cap = LubeConfig.tankCapacity_ml; + uint32_t remain10 = (PersistenceData.tankRemain_microL / 10); // keep your original math + uint32_t ratio = (cap > 0) ? (remain10 / cap) : 0; + temp.concat(String(ratio) + ";"); + temp.concat(String(PersistenceData.odometer + (PersistenceData.odometer_mm / 1000)) + ";"); if (client_id > 0) @@ -702,80 +833,76 @@ void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping) */ void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping) { - if (send_mapping) { - const char mapping[] = "MAPPING_STATIC:" - "lubedistancenormal;" - "lubedistancerain;" - "washdistance;" - "washinterval;" - "tankcap;" - "pumppulse;" - "tankwarn;" - "pulserev;" - "tirewidth;" - "tireratio;" - "tiredia;" - "speedsource;" - "gpsbaud;" - "cansource;" - "ledmodeflash;" - "ledmaxbrightness;" - "ledminbrightness;" - "showimpulse;" - "showgps;" - "showcan;" - "bleedingpulses;" - "wifi-ssid;" - "wifi-pass;"; - if (client_id > 0) - webSocket.text(client_id, mapping); + webSocket.text(client_id, kMappingStatic); else - webSocket.textAll(mapping); + webSocket.textAll(kMappingStatic); } String temp = "STATIC:"; - temp.concat(String(LubeConfig.DistancePerLube_Default) + ";"); - temp.concat(String(LubeConfig.DistancePerLube_Rain) + ";"); - temp.concat(String(LubeConfig.WashMode_Distance) + ";"); - temp.concat(String(LubeConfig.WashMode_Interval) + ";"); - temp.concat(String(LubeConfig.tankCapacity_ml) + ";"); - temp.concat(String(LubeConfig.amountPerDose_microL) + ";"); - temp.concat(String(LubeConfig.TankRemindAtPercentage) + ";"); - temp.concat(String(LubeConfig.PulsePerRevolution) + ";"); - temp.concat(String(LubeConfig.TireWidth_mm) + ";"); - temp.concat(String(LubeConfig.TireWidthHeight_Ratio) + ";"); - temp.concat(String(LubeConfig.RimDiameter_Inch) + ";"); - temp.concat(String(SpeedSourceString[LubeConfig.SpeedSource]) + ";"); - temp.concat(String(GPSBaudRateString[LubeConfig.GPSBaudRate]) + ";"); - temp.concat(String(CANSourceString[LubeConfig.CANSource]) + ";"); - temp.concat(String(LubeConfig.LED_Mode_Flash == true ? "1" : "0") + ";"); - temp.concat(String(LubeConfig.LED_Max_Brightness) + ";"); - temp.concat(String(LubeConfig.LED_Min_Brightness) + ";"); - temp.concat(String(LubeConfig.SpeedSource == SOURCE_IMPULSE ? "1" : "0") + ";"); - temp.concat(String(LubeConfig.SpeedSource == SOURCE_GPS ? "1" : "0") + ";"); - temp.concat(String(LubeConfig.SpeedSource == SOURCE_CAN ? "1" : "0") + ";"); - temp.concat(String(LubeConfig.BleedingPulses) + ";"); - temp.concat(String(LubeConfig.wifi_client_ssid) + ";"); - temp.concat(String(LubeConfig.wifi_client_password) + ";"); + temp += String(LubeConfig.DistancePerLube_Default) + ";"; + temp += String(LubeConfig.DistancePerLube_Rain) + ";"; + temp += String(LubeConfig.WashMode_Distance) + ";"; + temp += String(LubeConfig.WashMode_Interval) + ";"; + temp += String(LubeConfig.tankCapacity_ml) + ";"; + temp += String(LubeConfig.amountPerDose_microL) + ";"; + temp += String(LubeConfig.TankRemindAtPercentage) + ";"; + temp += String(LubeConfig.PulsePerRevolution) + ";"; + temp += String(LubeConfig.TireWidth_mm) + ";"; + temp += String(LubeConfig.TireWidthHeight_Ratio) + ";"; + temp += String(LubeConfig.RimDiameter_Inch) + ";"; - for (uint32_t i = 0; i < SpeedSourceString_Elements; i++) + // speedsource + Optionen + temp += tableStr(SpeedSourceString, (int)LubeConfig.SpeedSource, (int)SpeedSourceString_Elements) + ";"; { - temp.concat(String(SpeedSourceString[i]) + ","); + String csv; + appendCsv(csv, SpeedSourceString, SpeedSourceString_Elements); + temp += csv + ";"; } - temp.concat(";"); + + // gpsbaud + Optionen + temp += tableStr(GPSBaudRateString, (int)LubeConfig.GPSBaudRate, (int)GPSBaudRateString_Elements) + ";"; + { + String csv; + appendCsv(csv, GPSBaudRateString, GPSBaudRateString_Elements); + temp += csv + ";"; + } + + // cansource + Optionen + temp += tableStr(CANSourceString, (int)LubeConfig.CANSource, (int)CANSourceString_Elements) + ";"; + { + String csv; + appendCsv(csv, CANSourceString, CANSourceString_Elements); + temp += csv + ";"; + } + + temp += (LubeConfig.LED_Mode_Flash ? "1" : "0"); + temp += ";"; + temp += String(LubeConfig.LED_Max_Brightness) + ";"; + temp += String(LubeConfig.LED_Min_Brightness) + ";"; + temp += String(LubeConfig.SpeedSource == SOURCE_IMPULSE ? 1 : 0) + ";"; + temp += String(LubeConfig.SpeedSource == SOURCE_GPS ? 1 : 0) + ";"; + temp += String(LubeConfig.SpeedSource == SOURCE_CAN ? 1 : 0) + ";"; + temp += String(LubeConfig.BleedingPulses) + ";"; + temp += String(nz(LubeConfig.wifi_client_ssid)) + ";"; + temp += String(nz(LubeConfig.wifi_client_password)) + ";"; + + // Versionen (x.XX) + char ver_fw[8], ver_reqfs[8]; + snprintf(ver_fw, sizeof(ver_fw), "%u.%02u", constants.FW_Version_major, constants.FW_Version_minor); + snprintf(ver_reqfs, sizeof(ver_reqfs), "%u.%02u", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); + temp += String(ver_fw) + ";"; + temp += String(ver_reqfs) + ";"; + temp += String(nz(constants.GitHash)) + ";"; + temp += String(nz(globals.FlashVersion)) + ";"; if (client_id > 0) - { webSocket.text(client_id, temp); - } else - { webSocket.textAll(temp); - } } /** @@ -803,7 +930,7 @@ void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierB { // Kopieren des ersten Teils in den Buffer für Identifier strncpy(identifierBuffer, token, identifierBufferSize - 1); - identifierBuffer[identifierBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen + identifierBuffer[identifierBufferSize - 1] = '\0'; // Weitere Aufrufe von strtok, um den nächsten Teil zu erhalten token = strtok(NULL, ":"); @@ -813,7 +940,7 @@ void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierB { // Kopieren des zweiten Teils in den Buffer für Value strncpy(valueBuffer, token, valueBufferSize - 1); - valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen + valueBuffer[valueBufferSize - 1] = '\0'; } else { @@ -828,7 +955,7 @@ void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierB // Der gesamte String wird als Value betrachtet strncpy(valueBuffer, data, valueBufferSize - 1); - valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen + valueBuffer[valueBufferSize - 1] = '\0'; } } @@ -850,7 +977,7 @@ int findIndexByString(const char *searchString, const char *const *array, int ar // Durchlaufe das Array und vergleiche jeden String for (int i = 0; i < arraySize; ++i) { - if (strcmp(array[i], searchString) == 0) + if (array[i] && strcmp(array[i], searchString) == 0) { // String gefunden, gib den Index zurück return i; @@ -874,7 +1001,7 @@ int findIndexByString(const char *searchString, const char *const *array, int ar */ void Websocket_PushNotification(String Message, NotificationType_t type) { - String typeString = ""; + String typeString; switch (type) { case info: @@ -889,7 +1016,10 @@ void Websocket_PushNotification(String Message, NotificationType_t type) case error: typeString = "danger"; break; + default: + typeString = "info"; + break; } webSocket.textAll("NOTIFY:" + typeString + ";" + Message); - Debug_pushMessage("Sending Notification to WebUI: %s\n", typeString); + Debug_pushMessage("Sending Notification to WebUI: %s\n", typeString.c_str()); } \ No newline at end of file