diff --git a/data/firmware.bin b/data/firmware.bin
index 1d9b269..26b150b 100644
Binary files a/data/firmware.bin and b/data/firmware.bin differ
diff --git a/data/settings.html b/data/settings.html
index fd2552d..7e7e6aa 100644
--- a/data/settings.html
+++ b/data/settings.html
@@ -1,4 +1,4 @@
-
+
@@ -255,6 +255,7 @@
Verbundene Buttons:
Laden...
@@ -325,8 +326,9 @@
minute: "2-digit",
second: "2-digit",
});
- document.getElementById("currentTime").textContent =
- `System Zeit: ${timeString}`;
+ document.getElementById(
+ "currentTime"
+ ).textContent = `System Zeit: ${timeString}`;
} else {
document.getElementById("currentTime").textContent =
"Aktuelle Zeit: Fehler beim Laden";
@@ -356,7 +358,7 @@
})
.catch((error) => {
console.log(
- "Zeit konnte nicht geladen werden, verwende Browser-Zeit",
+ "Zeit konnte nicht geladen werden, verwende Browser-Zeit"
);
syncWithBrowserTime();
});
@@ -401,7 +403,7 @@
}
})
.catch((error) =>
- showMessage("Verbindungsfehler beim Setzen der Zeit", "error"),
+ showMessage("Verbindungsfehler beim Setzen der Zeit", "error")
);
}
@@ -438,7 +440,7 @@
}
})
.catch((error) =>
- showMessage("Verbindungsfehler beim Setzen der Zeit", "error"),
+ showMessage("Verbindungsfehler beim Setzen der Zeit", "error")
);
});
@@ -459,7 +461,7 @@
data.maxTimeDisplay || 20;
})
.catch((error) =>
- showMessage("Fehler beim Laden der Einstellungen", "error"),
+ showMessage("Fehler beim Laden der Einstellungen", "error")
);
}
@@ -474,6 +476,9 @@
data.channel || "Unbekannt";
document.getElementById("macAddress").textContent =
data.mac || "Unbekannt";
+ document.getElementById("isOnline").textContent = data.isOnline
+ ? "Ja"
+ : "Nein";
document.getElementById("freeMemory").textContent =
(data.freeMemory || 0) + " Bytes";
document.getElementById("connectedButtons").textContent =
@@ -499,7 +504,7 @@
loadLocations();
})
.catch((error) =>
- showMessage("Fehler beim Laden der Lizenz", "error"),
+ showMessage("Fehler beim Laden der Lizenz", "error")
);
}
@@ -537,7 +542,7 @@
data.password || "";
})
.catch((error) =>
- showMessage("Fehler beim Laden der WLAN-Einstellungen", "error"),
+ showMessage("Fehler beim Laden der WLAN-Einstellungen", "error")
);
}
@@ -559,7 +564,7 @@
}
if (
!confirm(
- "Der Server wird nach dem setzten neu gestartet. Fortsetzten?",
+ "Der Server wird nach dem setzten neu gestartet. Fortsetzten?"
)
) {
return;
@@ -570,19 +575,21 @@
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
- body: `ssid=${encodeURIComponent(ssid)}&password=${encodeURIComponent(password)}`,
+ body: `ssid=${encodeURIComponent(
+ ssid
+ )}&password=${encodeURIComponent(password)}`,
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
showMessage(
"WLAN-Einstellungen erfolgreich gespeichert!",
- "success",
+ "success"
);
} else {
showMessage(
data.error || "Fehler beim Speichern der WLAN-Einstellungen",
- "error",
+ "error"
);
}
})
@@ -593,10 +600,10 @@
function updateOTAButtonAccess(licenseLevel) {
const otaButton = document.getElementById("otaUpdateBtn");
const otarestrictionNotice = document.getElementById(
- "otaRestrictionNotice",
+ "otaRestrictionNotice"
);
const otacurrentLevelSpan = document.getElementById(
- "currentLicenseLevel",
+ "currentLicenseLevel"
);
const level = parseInt(licenseLevel) || 0;
@@ -625,14 +632,14 @@
if (response.status === 200) {
showMessage(
"Buttons führen das Update erfolgreich aus!",
- "success",
+ "success"
);
} else {
showMessage("Fehler beim Senden der MQTT Message", "error");
}
})
.catch((error) =>
- showMessage("Verbindungsfehler beim MQTT Publish", "error"),
+ showMessage("Verbindungsfehler beim MQTT Publish", "error")
);
}
}
@@ -641,10 +648,10 @@
const wifiSubmitBtn = document.getElementById("wifiSubmitBtn");
const wifiForm = document.getElementById("wifiForm");
const wifiRestrictionNotice = document.getElementById(
- "wifiRestrictionNotice",
+ "wifiRestrictionNotice"
);
const wifiCurrentLevelSpan = document.getElementById(
- "currentLicenseLevel",
+ "currentLicenseLevel"
);
const level = parseInt(licenseLevel) || 0;
@@ -679,14 +686,14 @@
) {
showMessage(
"OTA Update erfordert Lizenz Level 2 oder höher",
- "error",
+ "error"
);
return;
}
if (
confirm(
- "Möchten Sie wirklich ein OTA Update durchführen? Das Gerät wird während des Updates neu gestartet.",
+ "Möchten Sie wirklich ein OTA Update durchführen? Das Gerät wird während des Updates neu gestartet."
)
) {
window.location.href = "/update";
@@ -701,7 +708,7 @@
const maxTime = parseInt(document.getElementById("maxTime").value);
const maxTimeDisplay = parseInt(
- document.getElementById("maxTimeDisplay").value,
+ document.getElementById("maxTimeDisplay").value
);
fetch("/api/set-max-time", {
@@ -720,7 +727,7 @@
if (data.success) {
showMessage(
"Einstellungen erfolgreich gespeichert!",
- "success",
+ "success"
);
} else {
showMessage("Fehler beim Speichern der Einstellungen", "error");
@@ -738,7 +745,7 @@
if (data.success) {
showMessage(
"Beste Zeiten erfolgreich zurückgesetzt!",
- "success",
+ "success"
);
} else {
showMessage("Fehler beim Zurücksetzen", "error");
@@ -786,8 +793,9 @@
// Anlern-Anweisung aktualisieren
function updateLearningInstruction() {
if (learningStep < learningSteps.length) {
- document.getElementById("learningInstruction").innerHTML =
- `Drücken Sie jetzt den Button für:
${learningSteps[learningStep]}`;
+ document.getElementById(
+ "learningInstruction"
+ ).innerHTML = `Drücken Sie jetzt den Button für:
${learningSteps[learningStep]}`;
}
}
@@ -814,7 +822,7 @@
if (learningStep > 0) {
showMessage(
`${learningSteps[learningStep - 1]} erfolgreich angelernt!`,
- "success",
+ "success"
);
}
}
@@ -836,13 +844,13 @@
if (data.success) {
showMessage(
"Button-Zuweisungen erfolgreich zurückgesetzt!",
- "success",
+ "success"
);
loadSystemInfo();
} else {
showMessage(
"Fehler beim Zurücksetzen der Button-Zuweisungen",
- "error",
+ "error"
);
}
})
@@ -857,22 +865,46 @@
.then((data) => {
let statusText = "Button-Status:\n\n";
statusText += `Bahn 1 Start: ${
- data.lane1Start ? "Konfiguriert" : "Nicht konfiguriert"
+ data.lane1Start
+ ? `Konfiguriert${
+ data.lane1StartVoltage !== undefined
+ ? " (Batterystand: " + data.lane1StartVoltage + " %)"
+ : ""
+ }`
+ : "Nicht konfiguriert"
}\n`;
statusText += `Bahn 1 Stop: ${
- data.lane1Stop ? "Konfiguriert" : "Nicht konfiguriert"
+ data.lane1Stop
+ ? `Konfiguriert${
+ data.lane1StopVoltage !== undefined
+ ? " (Batterystand: " + data.lane1StopVoltage + " %)"
+ : ""
+ }`
+ : "Nicht konfiguriert"
}\n`;
statusText += `Bahn 2 Start: ${
- data.lane2Start ? "Konfiguriert" : "Nicht konfiguriert"
+ data.lane2Start
+ ? `Konfiguriert${
+ data.lane2StartVoltage !== undefined
+ ? " (Batterystand: " + data.lane2StartVoltage + " %)"
+ : ""
+ }`
+ : "Nicht konfiguriert"
}\n`;
statusText += `Bahn 2 Stop: ${
- data.lane2Stop ? "Konfiguriert" : "Nicht konfiguriert"
+ data.lane2Stop
+ ? `Konfiguriert${
+ data.lane2StopVoltage !== undefined
+ ? " (Batterystand: " + data.lane2StopVoltage + " %)"
+ : ""
+ }`
+ : "Nicht konfiguriert"
}\n`;
alert(statusText);
})
.catch((error) =>
- showMessage("Fehler beim Laden des Button-Status", "error"),
+ showMessage("Fehler beim Laden des Button-Status", "error")
);
}
@@ -925,7 +957,7 @@
.catch((error) => {
console.log(
"Aktueller Standort konnte nicht geladen werden:",
- error,
+ error
);
});
}
@@ -934,10 +966,10 @@
function updateLocationAccess(licenseLevel) {
const locationSubmitBtn = document.getElementById("locationSubmitBtn");
const locationRestrictionNotice = document.getElementById(
- "locationRestrictionNotice",
+ "locationRestrictionNotice"
);
const locationCurrentLevelSpan = document.getElementById(
- "currentLocationLicenseLevel",
+ "currentLocationLicenseLevel"
);
const locationSelect = document.getElementById("locationSelect");
@@ -975,7 +1007,7 @@
) {
showMessage(
"Standort-Konfiguration erfordert Lizenz Level 3 oder höher",
- "error",
+ "error"
);
return;
}
diff --git a/src/communication.h b/src/communication.h
index 2632b93..9da611a 100644
--- a/src/communication.h
+++ b/src/communication.h
@@ -157,59 +157,64 @@ void handleHeartbeatTopic(const char *topic, const char *payload) {
* berechnet den Ladezustand und sendet ein JSON an das Frontend.
*/
void handleBatteryTopic(const char *topic, const char *payload) {
- int batteryLevel = 0;
+ float batteryLevelV = 0;
+ int batteryLevelP = 0;
String topicStr(topic);
int lastSlash = topicStr.lastIndexOf('/');
if (lastSlash < 0)
return;
String macStr = topicStr.substring(lastSlash + 1);
- auto macBytes = macStringToBytes(macStr.c_str());
-
- String buttonType = "unknown";
- if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
- buttonType = "start1";
- } else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0) {
- buttonType = "stop1";
- } else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
- buttonType = "start2";
- } else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0) {
- buttonType = "stop2";
- }
-
// Parse payload for timestamp (optional, falls im Payload enthalten)
StaticJsonDocument<128> payloadDoc;
if (payload && strlen(payload) > 0 &&
deserializeJson(payloadDoc, payload) == DeserializationError::Ok) {
if (payloadDoc.containsKey("voltage")) {
- batteryLevel = payloadDoc["voltage"];
+ batteryLevelV = payloadDoc["voltage"];
}
}
// Berechne die Prozentzahl des Batteriestands für eine 1S LIPO batteryLevel
// sind Volts
// Hier wird angenommen, dass 3.7V 100% entspricht und 3.0V 0%
- batteryLevel =
- batteryLevel * 1000; // Umwandlung von V in mV für genauere Berechnung
- if (batteryLevel < 3200) {
- batteryLevel = 0; // 0% bei 3.0V
- } else if (batteryLevel > 3700) {
- batteryLevel = 100; // 100% bei 3.7V
+
+ if (batteryLevelV < 3200) {
+ batteryLevelP = 0; // 0% bei 3.2V
+ } else if (batteryLevelV > 3700) {
+ batteryLevelP = 100; // 100% bei 3.7V
} else {
- batteryLevel = map(batteryLevel, 3000, 3700, 0, 100); // Linear Mapping
+ batteryLevelP = map(batteryLevelV, 3200, 3700, 0, 100); // Linear Mapping
+ }
+
+ auto macBytes = macStringToBytes(macStr.c_str());
+ String buttonType = "unknown";
+ if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
+ buttonType = "start1";
+ buttonConfigs.start1.voltage = batteryLevelP;
+ } else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0) {
+ buttonType = "stop1";
+ buttonConfigs.stop1.voltage = batteryLevelP;
+ } else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
+ buttonType = "start2";
+ buttonConfigs.start2.voltage = batteryLevelP;
+ } else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0) {
+ buttonType = "stop2";
+ buttonConfigs.stop2.voltage = batteryLevelP;
}
// JSON bauen
StaticJsonDocument<128> doc;
doc["button"] = buttonType;
doc["mac"] = macStr;
- doc["batteryLevel"] = batteryLevel;
+ doc["batteryLevel"] = batteryLevelP;
String json;
serializeJson(doc, json);
pushUpdateToFrontend(
json); // Diese Funktion schickt das JSON an alle WebSocket-Clients
- // Serial.printf("Published heartbeat JSON: %s\n", json.c_str());
+
+ // Serial.printf("Battery level for %s (%s): %d%%\n", buttonType.c_str(),
+ macStr.c_str(), batteryLevelP);
}
/**
diff --git a/src/databasebackend.h b/src/databasebackend.h
index 1025ed6..7c1aa09 100644
--- a/src/databasebackend.h
+++ b/src/databasebackend.h
@@ -4,8 +4,8 @@
#include
const char *BACKEND_SERVER = "http://db.reptilfpv.de:3000";
-String BACKEND_TOKEN =
- licence; // Use the licence as the token for authentication
+extern String licence; // Declare licence as an external variable defined elsewhere
+String BACKEND_TOKEN = licence; // Use the licence as the token for authentication
bool backendOnline() {
diff --git a/src/helper.h b/src/helper.h
index c8529d0..80ffa4b 100644
--- a/src/helper.h
+++ b/src/helper.h
@@ -3,6 +3,7 @@
#include
+
std::array macStringToBytes(const char *macStr) {
std::array bytes;
sscanf(macStr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &bytes[0], &bytes[1],
diff --git a/src/master.h b/src/master.h
index 39c0c6c..5837ea2 100644
--- a/src/master.h
+++ b/src/master.h
@@ -33,6 +33,7 @@ struct TimerData {
struct ButtonConfig {
uint8_t mac[6];
bool isAssigned = false;
+ float voltage = 0.0;
};
struct ButtonConfigs {
diff --git a/src/timesync.h b/src/timesync.h
index ca37863..a6fc2b8 100644
--- a/src/timesync.h
+++ b/src/timesync.h
@@ -49,6 +49,29 @@ String getCurrentTimeJSON() {
return response;
}
+void syncTimeWithNTP(const char *ntpServer = "pool.ntp.org",
+ long gmtOffset_sec = 3600, int daylightOffset_sec = 0) {
+ configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
+ Serial.println("Warte auf NTP-Zeit (max 5s)...");
+ struct tm timeinfo;
+ unsigned long start = millis();
+ bool synced = false;
+ while (millis() - start < 5000) {
+ if (getLocalTime(&timeinfo, 10)) { // 10ms Timeout pro Versuch
+ synced = true;
+ break;
+ }
+ delay(10); // Kurze Pause, damit der Loop nicht blockiert
+ }
+ if (synced) {
+ Serial.println("\nNTP-Zeit synchronisiert!");
+ Serial.printf("Aktuelle Zeit: %02d:%02d:%02d\n", timeinfo.tm_hour,
+ timeinfo.tm_min, timeinfo.tm_sec);
+ } else {
+ Serial.println("\nNTP-Sync fehlgeschlagen (Timeout nach 5s)");
+ }
+}
+
// Hilfsfunktion: Setzt die Systemzeit auf den angegebenen Zeitstempel.
bool setSystemTime(long timestamp) {
struct timeval tv;
diff --git a/src/webserverrouter.h b/src/webserverrouter.h
index 9737c5a..224a219 100644
--- a/src/webserverrouter.h
+++ b/src/webserverrouter.h
@@ -143,9 +143,13 @@ void setupRoutes() {
[](AsyncWebServerRequest *request) {
DynamicJsonDocument doc(128);
doc["lane1Start"] = buttonConfigs.start1.isAssigned;
+ doc["lane1StartVoltage"] = buttonConfigs.start1.voltage;
doc["lane1Stop"] = buttonConfigs.stop1.isAssigned;
+ doc["lane1StopVoltage"] = buttonConfigs.stop1.voltage;
doc["lane2Start"] = buttonConfigs.start2.isAssigned;
+ doc["lane2StartVoltage"] = buttonConfigs.start2.voltage;
doc["lane2Stop"] = buttonConfigs.stop2.isAssigned;
+ doc["lane2StopVoltage"] = buttonConfigs.stop2.voltage;
String result;
serializeJson(doc, result);
request->send(200, "application/json", result);
@@ -181,7 +185,7 @@ void setupRoutes() {
if (buttonConfigs.stop2.isAssigned)
connected++;
doc["connectedButtons"] = connected;
-
+ doc["isOnline"] = isInternetAvailable() ? true : false;
doc["valid"] = checkLicence() > 0 ? "Ja" : "Nein";
doc["tier"] = checkLicence();
diff --git a/src/wificlass.h b/src/wificlass.h
index 7be0e60..7b6e477 100644
--- a/src/wificlass.h
+++ b/src/wificlass.h
@@ -8,12 +8,14 @@
#include "licenceing.h"
#include "master.h"
+#include "timesync.h"
String uniqueSSID;
PrettyOTA OTAUpdates;
String getUniqueSSID();
+bool isInternetAvailable();
// Initialisiert das WLAN als Access Point oder Station und startet mDNS/OTA.
void setupWifi() {
@@ -48,6 +50,9 @@ void setupWifi() {
Serial.println("Erfolgreich mit WLAN verbunden!");
Serial.print("IP Adresse: ");
Serial.println(WiFi.localIP());
+ if (isInternetAvailable) {
+ syncTimeWithNTP();
+ } // Synchronisiere Zeit mit NTP
}
// Only wait for connection if ssidSTA and passwordSTA are set
@@ -95,4 +100,9 @@ String getUniqueSSID() {
return String("AquaCross-") + String(uniqueId);
}
-// WiFi als Access Point
+// Prüft, ob das Internet erreichbar ist (z.B. durch Ping auf 8.8.8.8)
+bool isInternetAvailable() {
+ WiFiClient client;
+ // Versuche, eine Verbindung zu Googles DNS auf Port 53 herzustellen
+ return client.connect("8.8.8.8", 53);
+}