improved WebUI
This commit is contained in:
@@ -8,11 +8,11 @@
|
|||||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="static/css/custom.css">
|
<link rel="stylesheet" href="static/css/custom.css">
|
||||||
<link rel="stylesheet" href="static/css/tweaks.css">
|
<link rel="stylesheet" href="static/css/tweaks.css">
|
||||||
<script src="static/js/jquery.min.js"></script>
|
<script src="static/js/jquery.min.js" defer></script>
|
||||||
<script src="static/js/bootstrap.min.js"></script>
|
<script src="static/js/bootstrap.min.js" defer></script>
|
||||||
<script src="static/js/websocket.js"></script>
|
<script src="static/js/websocket.js" defer></script>
|
||||||
<script src="static/js/dtc_table.js"></script>
|
<script src="static/js/dtc_table.js" defer></script>
|
||||||
<script src="static/js/script.js"></script>
|
<script src="static/js/script.js" defer></script>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png">
|
||||||
@@ -217,9 +217,6 @@
|
|||||||
<label for="speedsource" class="control-label col-4">Schnittstelle</label>
|
<label for="speedsource" class="control-label col-4">Schnittstelle</label>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<select id="speedsource" class="set-wsevent data-speedsource select form-control">
|
<select id="speedsource" class="set-wsevent data-speedsource select form-control">
|
||||||
<option value="Impuls">Impuls</option>
|
|
||||||
<option value="GPS">GPS</option>
|
|
||||||
<option value="CAN-Bus">CAN-Bus</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,8 +295,6 @@
|
|||||||
<label for="cansource" class="control-label col-4">Model</label>
|
<label for="cansource" class="control-label col-4">Model</label>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<select id="cansource" class="set-wsevent data-cansource select form-control">
|
<select id="cansource" class="set-wsevent data-cansource select form-control">
|
||||||
<option value="KTM 890 Adventure R (2021)">KTM 890 Adventure R (2021)</option>
|
|
||||||
<option value="KTM 1290 Superduke R (2023)">KTM 1290 Superduke R (2023)</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -510,21 +505,26 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-7" scope="col">Parameter</td>
|
<th class="col-7" scope="col">Parameter</th>
|
||||||
<th class="col-5" scope="col">Value</td>
|
<th class="col-5" scope="col">Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Firmware Version</td>
|
<td>Firmware Version</td>
|
||||||
<td>%SW_VERSION%</td>
|
<td class="data-fw-version"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Flash Version</td>
|
<td>Benötigte Flash Version</td>
|
||||||
<td>%FS_VERSION%</td>
|
<td class="data-req-flash-version"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Installierte Flash Version</td>
|
||||||
|
<td class="data-flash-version"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Git Revision</td>
|
<td>Git Revision</td>
|
||||||
<td>%GIT_REV%</td>
|
<td class="data-git-rev"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</p>
|
</p>
|
||||||
<!-- Div Group VersionInfo -->
|
<!-- Div Group VersionInfo -->
|
||||||
|
@@ -84,7 +84,6 @@ function onMessage(event) {
|
|||||||
|
|
||||||
processDTCNotifications(dtcArray);
|
processDTCNotifications(dtcArray);
|
||||||
fillDTCTable(dtcArray);
|
fillDTCTable(dtcArray);
|
||||||
|
|
||||||
} else if (data.startsWith("MAPPING_STATUS:")) {
|
} else if (data.startsWith("MAPPING_STATUS:")) {
|
||||||
const data_sliced = data.slice(15);
|
const data_sliced = data.slice(15);
|
||||||
statusMapping = createMapping(data_sliced);
|
statusMapping = createMapping(data_sliced);
|
||||||
@@ -100,19 +99,15 @@ function onMessage(event) {
|
|||||||
const data_sliced = data.slice(7);
|
const data_sliced = data.slice(7);
|
||||||
const result = processDataString(data_sliced, staticMapping);
|
const result = processDataString(data_sliced, staticMapping);
|
||||||
fillValuesToHTML(result);
|
fillValuesToHTML(result);
|
||||||
console.log(result);
|
|
||||||
overlay.style.display = "none";
|
overlay.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMapping(mappingString) {
|
function createMapping(mappingString) {
|
||||||
const mappingArray = mappingString.split(";");
|
return mappingString
|
||||||
const mapping = [];
|
.split(";")
|
||||||
|
.map((s) => s.trim())
|
||||||
mappingArray.forEach((variable) => {
|
.filter((s) => s !== "");
|
||||||
if (variable !== null) mapping.push(variable.trim());
|
|
||||||
});
|
|
||||||
return mapping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDataString(dataString, mapping) {
|
function processDataString(dataString, mapping) {
|
||||||
@@ -157,43 +152,136 @@ function do_resize(textbox) {
|
|||||||
else textbox.rows = rows;
|
else textbox.rows = rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillValuesToHTML(dataset) {
|
// --- Globale Puffer für Select-Handling ---
|
||||||
for (var key in dataset) {
|
const selectDesiredValue = Object.create(null); // keyBase -> desired value ("speedsource" -> "GPS")
|
||||||
var key_prefixed = "data-" + key;
|
const selectOptionsReady = Object.create(null); // keyBase -> true/false
|
||||||
var elements = document.getElementsByClassName(key_prefixed);
|
|
||||||
|
|
||||||
if (elements.length > 0) {
|
function splitCsv(s) {
|
||||||
for (var i = 0; i < elements.length; i++) {
|
return (s || "")
|
||||||
var element = elements[i];
|
.split(",")
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
if (element.type === "checkbox") {
|
function hasOption(selectEl, value) {
|
||||||
// Wenn das Element ein Kontrollkästchen ist
|
for (let i = 0; i < selectEl.options.length; i++) {
|
||||||
element.checked = dataset[key] == 1 ? true : false;
|
if (selectEl.options[i].value === value) return true;
|
||||||
} 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
function setDropdownValue(selectElement, value) {
|
||||||
for (var i = 0; i < selectElement.options.length; i++) {
|
if (!value) return;
|
||||||
if (selectElement.options[i].value === value) {
|
if (!hasOption(selectElement, value)) {
|
||||||
selectElement.selectedIndex = i;
|
addOption(selectElement, value, false);
|
||||||
break;
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "webui.h"
|
#include "webui.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <cstring> // strlen, strncpy, memcpy
|
||||||
|
#include <algorithm> // std::clamp
|
||||||
|
|
||||||
AsyncWebServer webServer(80);
|
AsyncWebServer webServer(80);
|
||||||
|
|
||||||
@@ -19,6 +23,28 @@ const char *PARAM_MESSAGE = "message";
|
|||||||
|
|
||||||
SpeedSource_t speedsourcePreselect; /**< Preselect Memory for change SourceAdress */
|
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);
|
String processor(const String &var);
|
||||||
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
|
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
|
||||||
void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
|
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);
|
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);
|
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.
|
* @brief Initializes the web-based user interface (WebUI) for the ChainLube application.
|
||||||
*
|
*
|
||||||
@@ -116,6 +170,54 @@ void Webserver_Process()
|
|||||||
Websocket_RefreshClientData_Status(0);
|
Websocket_RefreshClientData_Status(0);
|
||||||
previousMillis = millis();
|
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");
|
File this_file = LittleFS.open("version", "r");
|
||||||
if (!this_file)
|
if (!this_file)
|
||||||
{ // failed to open the file, retrn empty result
|
{ // failed to open the file, return empty result
|
||||||
buff[0] = '\0';
|
buff[0] = '\0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this_file.available())
|
if (this_file.available())
|
||||||
{
|
{
|
||||||
int bytes_read;
|
int bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1);
|
||||||
bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1);
|
if (bytes_read < 0)
|
||||||
|
bytes_read = 0;
|
||||||
buff[bytes_read] = '\0';
|
buff[bytes_read] = '\0';
|
||||||
}
|
}
|
||||||
this_file.close();
|
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);
|
memcpy(buffer + read_ptr, data, len);
|
||||||
read_ptr = read_ptr + len;
|
read_ptr = read_ptr + len;
|
||||||
@@ -280,6 +383,11 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f
|
|||||||
{
|
{
|
||||||
if (buffer != NULL)
|
if (buffer != NULL)
|
||||||
{
|
{
|
||||||
|
// Ensure zero-termination just in case
|
||||||
|
if (read_ptr >= 1536)
|
||||||
|
read_ptr = 1535;
|
||||||
|
buffer[read_ptr] = '\0';
|
||||||
|
|
||||||
Serial.print(buffer);
|
Serial.print(buffer);
|
||||||
JsonDocument json;
|
JsonDocument json;
|
||||||
error = deserializeJson(json, buffer);
|
error = deserializeJson(json, buffer);
|
||||||
@@ -323,7 +431,11 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer)
|
||||||
|
{
|
||||||
free(buffer);
|
free(buffer);
|
||||||
|
buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
|
AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
|
||||||
response->addHeader("Refresh", "20");
|
response->addHeader("Refresh", "20");
|
||||||
@@ -354,10 +466,10 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request)
|
|||||||
|
|
||||||
char buffer[16];
|
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);
|
sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor);
|
||||||
info["FW-Version"] = buffer;
|
info["FW-Version"] = buffer;
|
||||||
info["FS-Version"] = globals.FlashVersion;
|
info["FS-Version"] = nz(globals.FlashVersion);
|
||||||
snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash);
|
snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash);
|
||||||
info["Git-Hash"] = buffer;
|
info["Git-Hash"] = buffer;
|
||||||
|
|
||||||
@@ -395,11 +507,16 @@ void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *clien
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case WS_EVT_CONNECT:
|
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);
|
Debug_pushMessage("WebSocket client #%u connected from %s\n",
|
||||||
Websocket_RefreshClientData_Static(client->id(), true);
|
client->id(), client->remoteIP().toString().c_str());
|
||||||
Websocket_RefreshClientData_DTCs(client->id());
|
// 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;
|
break;
|
||||||
|
}
|
||||||
case WS_EVT_DISCONNECT:
|
case WS_EVT_DISCONNECT:
|
||||||
Debug_pushMessage("WebSocket client #%u disconnected\n", client->id());
|
Debug_pushMessage("WebSocket client #%u disconnected\n", client->id());
|
||||||
break;
|
break;
|
||||||
@@ -427,20 +544,24 @@ void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len)
|
|||||||
AwsFrameInfo *info = (AwsFrameInfo *)arg;
|
AwsFrameInfo *info = (AwsFrameInfo *)arg;
|
||||||
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
|
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
|
||||||
{
|
{
|
||||||
data[len] = 0;
|
// Create a safe, null-terminated local copy
|
||||||
Debug_pushMessage("Websocket-Message (len: %d): %s\n", len, (char *)data);
|
std::unique_ptr<char[]> 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
|
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)
|
else if (strcmp(identifier, "speedsource") == 0)
|
||||||
{
|
{
|
||||||
int index = findIndexByString(value, SpeedSourceString, SpeedSourceString_Elements);
|
int index = findIndexByString(value, SpeedSourceString, (int)SpeedSourceString_Elements);
|
||||||
|
if (validIndex(index, (int)SpeedSourceString_Elements))
|
||||||
speedsourcePreselect = (SpeedSource_t)index;
|
speedsourcePreselect = (SpeedSource_t)index;
|
||||||
|
else
|
||||||
|
Debug_pushMessage("Invalid speedsource '%s'\n", value);
|
||||||
}
|
}
|
||||||
else if (strcmp(identifier, "cansource") == 0)
|
else if (strcmp(identifier, "cansource") == 0)
|
||||||
{
|
{
|
||||||
int index = findIndexByString(value, CANSourceString, CANSourceString_Elements);
|
int index = findIndexByString(value, CANSourceString, (int)CANSourceString_Elements);
|
||||||
|
if (validIndex(index, (int)CANSourceString_Elements))
|
||||||
LubeConfig.CANSource = (CANSource_t)index;
|
LubeConfig.CANSource = (CANSource_t)index;
|
||||||
|
else
|
||||||
|
Debug_pushMessage("Invalid cansource '%s'\n", value);
|
||||||
}
|
}
|
||||||
else if (strcmp(identifier, "gpsbaud") == 0)
|
else if (strcmp(identifier, "gpsbaud") == 0)
|
||||||
{
|
{
|
||||||
int index = findIndexByString(value, GPSBaudRateString, GPSBaudRateString_Elements);
|
int index = findIndexByString(value, GPSBaudRateString, (int)GPSBaudRateString_Elements);
|
||||||
|
if (validIndex(index, (int)GPSBaudRateString_Elements))
|
||||||
LubeConfig.GPSBaudRate = (GPSBaudRate_t)index;
|
LubeConfig.GPSBaudRate = (GPSBaudRate_t)index;
|
||||||
|
else
|
||||||
|
Debug_pushMessage("Invalid gpsbaud '%s'\n", value);
|
||||||
}
|
}
|
||||||
else if (strcmp(identifier, "ledmaxbrightness") == 0)
|
else if (strcmp(identifier, "ledmaxbrightness") == 0)
|
||||||
{
|
{
|
||||||
@@ -570,7 +700,7 @@ void Websocket_HandleSettings(uint8_t *data)
|
|||||||
}
|
}
|
||||||
else if (strcmp(identifier, "ledmodeflash") == 0)
|
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)
|
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)
|
if (send_mapping)
|
||||||
{
|
{
|
||||||
const char mapping[] = "MAPPING_STATUS:"
|
|
||||||
"systemstatus;"
|
|
||||||
"tankremain;"
|
|
||||||
"odometer;";
|
|
||||||
|
|
||||||
if (client_id > 0)
|
if (client_id > 0)
|
||||||
webSocket.text(client_id, mapping);
|
webSocket.text(client_id, kMappingStatus);
|
||||||
else
|
else
|
||||||
webSocket.textAll(mapping);
|
webSocket.textAll(kMappingStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
String temp = "STATUS:";
|
String temp = "STATUS:";
|
||||||
|
|
||||||
temp.concat(String(globals.systemStatustxt) + ";");
|
temp.concat(String(nz(globals.systemStatustxt)) + ";");
|
||||||
temp.concat(String((PersistenceData.tankRemain_microL / 10) / LubeConfig.tankCapacity_ml) + ";");
|
|
||||||
|
// 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)) + ";");
|
temp.concat(String(PersistenceData.odometer + (PersistenceData.odometer_mm / 1000)) + ";");
|
||||||
|
|
||||||
if (client_id > 0)
|
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)
|
void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (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)
|
if (client_id > 0)
|
||||||
webSocket.text(client_id, mapping);
|
webSocket.text(client_id, kMappingStatic);
|
||||||
else
|
else
|
||||||
webSocket.textAll(mapping);
|
webSocket.textAll(kMappingStatic);
|
||||||
}
|
}
|
||||||
|
|
||||||
String temp = "STATIC:";
|
String temp = "STATIC:";
|
||||||
|
|
||||||
temp.concat(String(LubeConfig.DistancePerLube_Default) + ";");
|
temp += String(LubeConfig.DistancePerLube_Default) + ";";
|
||||||
temp.concat(String(LubeConfig.DistancePerLube_Rain) + ";");
|
temp += String(LubeConfig.DistancePerLube_Rain) + ";";
|
||||||
temp.concat(String(LubeConfig.WashMode_Distance) + ";");
|
temp += String(LubeConfig.WashMode_Distance) + ";";
|
||||||
temp.concat(String(LubeConfig.WashMode_Interval) + ";");
|
temp += String(LubeConfig.WashMode_Interval) + ";";
|
||||||
temp.concat(String(LubeConfig.tankCapacity_ml) + ";");
|
temp += String(LubeConfig.tankCapacity_ml) + ";";
|
||||||
temp.concat(String(LubeConfig.amountPerDose_microL) + ";");
|
temp += String(LubeConfig.amountPerDose_microL) + ";";
|
||||||
temp.concat(String(LubeConfig.TankRemindAtPercentage) + ";");
|
temp += String(LubeConfig.TankRemindAtPercentage) + ";";
|
||||||
temp.concat(String(LubeConfig.PulsePerRevolution) + ";");
|
temp += String(LubeConfig.PulsePerRevolution) + ";";
|
||||||
temp.concat(String(LubeConfig.TireWidth_mm) + ";");
|
temp += String(LubeConfig.TireWidth_mm) + ";";
|
||||||
temp.concat(String(LubeConfig.TireWidthHeight_Ratio) + ";");
|
temp += String(LubeConfig.TireWidthHeight_Ratio) + ";";
|
||||||
temp.concat(String(LubeConfig.RimDiameter_Inch) + ";");
|
temp += 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) + ";");
|
|
||||||
|
|
||||||
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)
|
if (client_id > 0)
|
||||||
{
|
|
||||||
webSocket.text(client_id, temp);
|
webSocket.text(client_id, temp);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
webSocket.textAll(temp);
|
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
|
// Kopieren des ersten Teils in den Buffer für Identifier
|
||||||
strncpy(identifierBuffer, token, identifierBufferSize - 1);
|
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
|
// Weitere Aufrufe von strtok, um den nächsten Teil zu erhalten
|
||||||
token = strtok(NULL, ":");
|
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
|
// Kopieren des zweiten Teils in den Buffer für Value
|
||||||
strncpy(valueBuffer, token, valueBufferSize - 1);
|
strncpy(valueBuffer, token, valueBufferSize - 1);
|
||||||
valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen
|
valueBuffer[valueBufferSize - 1] = '\0';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -828,7 +955,7 @@ void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierB
|
|||||||
|
|
||||||
// Der gesamte String wird als Value betrachtet
|
// Der gesamte String wird als Value betrachtet
|
||||||
strncpy(valueBuffer, data, valueBufferSize - 1);
|
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
|
// Durchlaufe das Array und vergleiche jeden String
|
||||||
for (int i = 0; i < arraySize; ++i)
|
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
|
// String gefunden, gib den Index zurück
|
||||||
return i;
|
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)
|
void Websocket_PushNotification(String Message, NotificationType_t type)
|
||||||
{
|
{
|
||||||
String typeString = "";
|
String typeString;
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case info:
|
case info:
|
||||||
@@ -889,7 +1016,10 @@ void Websocket_PushNotification(String Message, NotificationType_t type)
|
|||||||
case error:
|
case error:
|
||||||
typeString = "danger";
|
typeString = "danger";
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
typeString = "info";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
webSocket.textAll("NOTIFY:" + typeString + ";" + Message);
|
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());
|
||||||
}
|
}
|
Reference in New Issue
Block a user