move hardbeat handling into backend, add publish lanestate for lighttower
This commit is contained in:
Binary file not shown.
@@ -144,7 +144,26 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
console.log("WebSocket message received:", event.data);
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.button && data.mac && data.active !== undefined) {
|
||||||
|
const indicatorId =
|
||||||
|
data.button === "start1"
|
||||||
|
? "heartbeat1"
|
||||||
|
: data.button === "stop1"
|
||||||
|
? "heartbeat2"
|
||||||
|
: data.button === "start2"
|
||||||
|
? "heartbeat3"
|
||||||
|
: data.button === "stop2"
|
||||||
|
? "heartbeat4"
|
||||||
|
: null;
|
||||||
|
if (indicatorId) {
|
||||||
|
if (data.active) {
|
||||||
|
document.getElementById(indicatorId).classList.add("active");
|
||||||
|
} else {
|
||||||
|
document.getElementById(indicatorId).classList.remove("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
@@ -328,20 +347,23 @@
|
|||||||
let display1 = timer1;
|
let display1 = timer1;
|
||||||
let display2 = timer2;
|
let display2 = timer2;
|
||||||
|
|
||||||
if (status1 === "running") {
|
// Status für Bahn 1
|
||||||
|
const s1 = document.getElementById("status1");
|
||||||
|
const lane1Connected = areBothButtonsConnected(1);
|
||||||
|
// Status für Bahn 2
|
||||||
|
const s2 = document.getElementById("status2");
|
||||||
|
const lane2Connected = areBothButtonsConnected(2);
|
||||||
|
|
||||||
|
if (status1 === "running" && lane1Connected) {
|
||||||
display1 += (now - lastSync) / 1000;
|
display1 += (now - lastSync) / 1000;
|
||||||
}
|
}
|
||||||
if (status2 === "running") {
|
if (status2 === "running" && lane2Connected) {
|
||||||
display2 += (now - lastSync) / 1000;
|
display2 += (now - lastSync) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("time1").textContent = formatTime(display1);
|
document.getElementById("time1").textContent = formatTime(display1);
|
||||||
|
|
||||||
// Status für Bahn 1
|
if (!lane1Connected) {
|
||||||
const s1 = document.getElementById("status1");
|
|
||||||
const lane1Connected = areBothButtonsConnected(1);
|
|
||||||
|
|
||||||
if (status1 === "ready" && !lane1Connected) {
|
|
||||||
s1.className = "status standby";
|
s1.className = "status standby";
|
||||||
s1.textContent = "Standby: Bitte beide Buttons 1x betätigen";
|
s1.textContent = "Standby: Bitte beide Buttons 1x betätigen";
|
||||||
} else {
|
} else {
|
||||||
@@ -356,13 +378,9 @@
|
|||||||
|
|
||||||
document.getElementById("time2").textContent = formatTime(display2);
|
document.getElementById("time2").textContent = formatTime(display2);
|
||||||
|
|
||||||
// Status für Bahn 2
|
if (!lane2Connected) {
|
||||||
const s2 = document.getElementById("status2");
|
|
||||||
const lane2Connected = areBothButtonsConnected(2);
|
|
||||||
|
|
||||||
if (status2 === "ready" && !lane2Connected) {
|
|
||||||
s2.className = "status standby";
|
s2.className = "status standby";
|
||||||
s2.textContent = "Standby: Bitte beide Buttons 1x betätigen";
|
s2.textContent = "Standby: Bitte beide 1x betätigen";
|
||||||
} else {
|
} else {
|
||||||
s2.className = `status ${status2}`;
|
s2.className = `status ${status2}`;
|
||||||
s2.textContent =
|
s2.textContent =
|
||||||
|
|||||||
@@ -89,3 +89,19 @@ lib_deps =
|
|||||||
mlesniew/PicoMQTT@^1.3.0
|
mlesniew/PicoMQTT@^1.3.0
|
||||||
miguelbalboa/MFRC522@^1.4.12
|
miguelbalboa/MFRC522@^1.4.12
|
||||||
adafruit/RTClib@^2.1.4
|
adafruit/RTClib@^2.1.4
|
||||||
|
|
||||||
|
[env:esp32-s3-devkitc-1]
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_upload.flash_size = 16MB
|
||||||
|
board_build.partitions = default_16MB.csv
|
||||||
|
build_flags =
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-DBATTERY_PIN=35
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson@^7.4.1
|
||||||
|
esp32async/ESPAsyncWebServer@^3.7.7
|
||||||
|
esp32async/AsyncTCP@^3.4.2
|
||||||
|
mlesniew/PicoMQTT@^1.3.0
|
||||||
|
miguelbalboa/MFRC522@^1.4.12
|
||||||
|
adafruit/RTClib@^2.1.4
|
||||||
@@ -109,7 +109,6 @@ void readButtonJSON(const char *topic, const char *payload) {
|
|||||||
* extrahiert MAC und Timestamp und sendet ein JSON an das Frontend.
|
* extrahiert MAC und Timestamp und sendet ein JSON an das Frontend.
|
||||||
*/
|
*/
|
||||||
void handleHeartbeatTopic(const char *topic, const char *payload) {
|
void handleHeartbeatTopic(const char *topic, const char *payload) {
|
||||||
// Topic-Format: heartbeat/alive/CC:DB:A7:2F:95:08
|
|
||||||
String topicStr(topic);
|
String topicStr(topic);
|
||||||
int lastSlash = topicStr.lastIndexOf('/');
|
int lastSlash = topicStr.lastIndexOf('/');
|
||||||
if (lastSlash < 0)
|
if (lastSlash < 0)
|
||||||
@@ -119,17 +118,22 @@ void handleHeartbeatTopic(const char *topic, const char *payload) {
|
|||||||
auto macBytes = macStringToBytes(macStr.c_str());
|
auto macBytes = macStringToBytes(macStr.c_str());
|
||||||
|
|
||||||
String buttonType = "unknown";
|
String buttonType = "unknown";
|
||||||
|
ButtonConfig *btn = nullptr;
|
||||||
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
|
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
|
||||||
buttonType = "start1";
|
buttonType = "start1";
|
||||||
|
btn = &buttonConfigs.start1;
|
||||||
} else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0) {
|
} else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0) {
|
||||||
buttonType = "stop1";
|
buttonType = "stop1";
|
||||||
|
btn = &buttonConfigs.stop1;
|
||||||
} else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
|
} else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
|
||||||
buttonType = "start2";
|
buttonType = "start2";
|
||||||
|
btn = &buttonConfigs.start2;
|
||||||
} else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0) {
|
} else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0) {
|
||||||
buttonType = "stop2";
|
buttonType = "stop2";
|
||||||
|
btn = &buttonConfigs.stop2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse payload for timestamp (optional, falls im Payload enthalten)
|
// Parse payload for timestamp (optional)
|
||||||
uint64_t timestamp = millis();
|
uint64_t timestamp = millis();
|
||||||
StaticJsonDocument<128> payloadDoc;
|
StaticJsonDocument<128> payloadDoc;
|
||||||
if (payload && strlen(payload) > 0 &&
|
if (payload && strlen(payload) > 0 &&
|
||||||
@@ -139,17 +143,22 @@ void handleHeartbeatTopic(const char *topic, const char *payload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update heartbeat info
|
||||||
|
if (btn) {
|
||||||
|
btn->lastHeartbeat = millis();
|
||||||
|
btn->heartbeatActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
// JSON bauen
|
// JSON bauen
|
||||||
StaticJsonDocument<128> doc;
|
StaticJsonDocument<128> doc;
|
||||||
doc["button"] = buttonType;
|
doc["button"] = buttonType;
|
||||||
doc["mac"] = macStr;
|
doc["mac"] = macStr;
|
||||||
doc["timestamp"] = timestamp;
|
doc["timestamp"] = timestamp;
|
||||||
|
doc["active"] = true; // always true on heartbeat
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
serializeJson(doc, json);
|
serializeJson(doc, json);
|
||||||
pushUpdateToFrontend(
|
pushUpdateToFrontend(json);
|
||||||
json); // Diese Funktion schickt das JSON an alle WebSocket-Clients
|
|
||||||
// Serial.printf("Published heartbeat JSON: %s\n", json.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,7 +222,21 @@ void handleBatteryTopic(const char *topic, const char *payload) {
|
|||||||
pushUpdateToFrontend(
|
pushUpdateToFrontend(
|
||||||
json); // Diese Funktion schickt das JSON an alle WebSocket-Clients
|
json); // Diese Funktion schickt das JSON an alle WebSocket-Clients
|
||||||
|
|
||||||
// Serial.printf("Battery level for %s (%s): %d%%\n", buttonType.c_str(),macStr.c_str(), batteryLevelP);
|
// Serial.printf("Battery level for %s (%s): %d%%\n",
|
||||||
|
// buttonType.c_str(),macStr.c_str(), batteryLevelP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void publishLaneStatus(int lane, String status) {
|
||||||
|
JsonDocument messageDoc;
|
||||||
|
messageDoc["lane"] = lane;
|
||||||
|
messageDoc["status"] = status;
|
||||||
|
|
||||||
|
String message;
|
||||||
|
serializeJson(messageDoc, message);
|
||||||
|
|
||||||
|
// Topic dynamisch nach Bahn wählen
|
||||||
|
String topic = "aquacross/lanes/lane" + String(lane);
|
||||||
|
mqtt.publish(topic.c_str(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -366,3 +389,32 @@ void sendMQTTJSONMessage(const char *topic, const JsonDocument &doc) {
|
|||||||
Serial.printf("Published JSON message to topic '%s': %s\n", topic,
|
Serial.printf("Published JSON message to topic '%s': %s\n", topic,
|
||||||
jsonString.c_str());
|
jsonString.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkHeartbeatTimeouts(unsigned long timeoutMs = 10000) {
|
||||||
|
struct ButtonRef {
|
||||||
|
ButtonConfig &btn;
|
||||||
|
const char *type;
|
||||||
|
};
|
||||||
|
|
||||||
|
ButtonRef buttons[] = {{buttonConfigs.start1, "start1"},
|
||||||
|
{buttonConfigs.stop1, "stop1"},
|
||||||
|
{buttonConfigs.start2, "start2"},
|
||||||
|
{buttonConfigs.stop2, "stop2"}};
|
||||||
|
|
||||||
|
unsigned long now = millis();
|
||||||
|
for (auto &b : buttons) {
|
||||||
|
if (b.btn.isAssigned && b.btn.heartbeatActive &&
|
||||||
|
now - b.btn.lastHeartbeat > timeoutMs) {
|
||||||
|
b.btn.heartbeatActive = false;
|
||||||
|
|
||||||
|
// Send inactive status to frontend
|
||||||
|
StaticJsonDocument<128> doc;
|
||||||
|
doc["button"] = b.type;
|
||||||
|
doc["mac"] = b.btn.mac;
|
||||||
|
doc["active"] = false;
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
pushUpdateToFrontend(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ std::array<uint8_t, 6> macStringToBytes(const char *macStr) {
|
|||||||
&bytes[2], &bytes[3], &bytes[4], &bytes[5]);
|
&bytes[2], &bytes[3], &bytes[4], &bytes[5]);
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string macToString(const std::array<uint8_t, 6> &macBytes) {
|
||||||
|
char macStr[18];
|
||||||
|
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", macBytes[0],
|
||||||
|
macBytes[1], macBytes[2], macBytes[3], macBytes[4], macBytes[5]);
|
||||||
|
return std::string(macStr);
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ void handleStart1(uint64_t timestamp = 0) {
|
|||||||
timerData.localStartTime1 = millis(); // Set local start time
|
timerData.localStartTime1 = millis(); // Set local start time
|
||||||
timerData.isRunning1 = true;
|
timerData.isRunning1 = true;
|
||||||
timerData.endTime1 = 0;
|
timerData.endTime1 = 0;
|
||||||
|
publishLaneStatus(1, "running");
|
||||||
Serial.println("Bahn 1 gestartet");
|
Serial.println("Bahn 1 gestartet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,7 @@ void handleStop1(uint64_t timestamp = 0) {
|
|||||||
timerData.bestTime1 = currentTime;
|
timerData.bestTime1 = currentTime;
|
||||||
saveBestTimes();
|
saveBestTimes();
|
||||||
}
|
}
|
||||||
|
publishLaneStatus(1, "stopped");
|
||||||
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
||||||
"s");
|
"s");
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,7 @@ void handleStart2(uint64_t timestamp = 0) {
|
|||||||
timerData.localStartTime2 = millis(); // Set local start time
|
timerData.localStartTime2 = millis(); // Set local start time
|
||||||
timerData.isRunning2 = true;
|
timerData.isRunning2 = true;
|
||||||
timerData.endTime2 = 0;
|
timerData.endTime2 = 0;
|
||||||
|
publishLaneStatus(2, "running");
|
||||||
Serial.println("Bahn 2 gestartet");
|
Serial.println("Bahn 2 gestartet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +76,7 @@ void handleStop2(uint64_t timestamp = 0) {
|
|||||||
timerData.bestTime2 = currentTime;
|
timerData.bestTime2 = currentTime;
|
||||||
saveBestTimes();
|
saveBestTimes();
|
||||||
}
|
}
|
||||||
|
publishLaneStatus(2, "stopped");
|
||||||
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
||||||
"s");
|
"s");
|
||||||
}
|
}
|
||||||
@@ -85,6 +89,7 @@ void checkAutoReset() {
|
|||||||
(currentTime - timerData.localStartTime1 > maxTimeBeforeReset)) {
|
(currentTime - timerData.localStartTime1 > maxTimeBeforeReset)) {
|
||||||
timerData.isRunning1 = false;
|
timerData.isRunning1 = false;
|
||||||
timerData.startTime1 = 0;
|
timerData.startTime1 = 0;
|
||||||
|
publishLaneStatus(1, "ready");
|
||||||
Serial.println("Bahn 1 automatisch zurückgesetzt");
|
Serial.println("Bahn 1 automatisch zurückgesetzt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +97,7 @@ void checkAutoReset() {
|
|||||||
(currentTime - timerData.localStartTime2 > maxTimeBeforeReset)) {
|
(currentTime - timerData.localStartTime2 > maxTimeBeforeReset)) {
|
||||||
timerData.isRunning2 = false;
|
timerData.isRunning2 = false;
|
||||||
timerData.startTime2 = 0;
|
timerData.startTime2 = 0;
|
||||||
|
publishLaneStatus(2, "ready");
|
||||||
Serial.println("Bahn 2 automatisch zurückgesetzt");
|
Serial.println("Bahn 2 automatisch zurückgesetzt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +120,7 @@ void checkAutoReset() {
|
|||||||
|
|
||||||
// Push the message to the frontend
|
// Push the message to the frontend
|
||||||
pushUpdateToFrontend(message);
|
pushUpdateToFrontend(message);
|
||||||
|
publishLaneStatus(1, "ready");
|
||||||
|
|
||||||
Serial.println("Bahn 1 automatisch auf 'Bereit' zurückgesetzt");
|
Serial.println("Bahn 1 automatisch auf 'Bereit' zurückgesetzt");
|
||||||
}
|
}
|
||||||
@@ -137,6 +144,7 @@ void checkAutoReset() {
|
|||||||
|
|
||||||
// Push the message to the frontend
|
// Push the message to the frontend
|
||||||
pushUpdateToFrontend(message);
|
pushUpdateToFrontend(message);
|
||||||
|
publishLaneStatus(2, "ready");
|
||||||
|
|
||||||
Serial.println("Bahn 2 automatisch auf 'Bereit' zurückgesetzt");
|
Serial.println("Bahn 2 automatisch auf 'Bereit' zurückgesetzt");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ struct ButtonConfig {
|
|||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
bool isAssigned = false;
|
bool isAssigned = false;
|
||||||
float voltage = 0.0;
|
float voltage = 0.0;
|
||||||
|
unsigned long lastHeartbeat = 0; // Zeit des letzten Heartbeats (millis)
|
||||||
|
bool heartbeatActive = false; // Status: aktiv/inaktiv
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ButtonConfigs {
|
struct ButtonConfigs {
|
||||||
|
|||||||
Reference in New Issue
Block a user