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
+ | Parameter |
+ Value |
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 = $(
'' +
- "" +
- message +
- "" +
- '" +
- "
"
+ 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