MQTT messages. Button anleren, Wifi frontend

This commit is contained in:
Carsten Graf
2025-06-05 23:34:11 +02:00
parent 3b4f63f072
commit e0d3031d6f
10 changed files with 269 additions and 114 deletions

View File

@@ -10,7 +10,10 @@
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"regex": "cpp"
"regex": "cpp",
"memory": "cpp",
"xstring": "cpp",
"xutility": "cpp"
},
"liveServer.settings.multiRootWorkspaceName": "AquaMasterMQTT"
}

View File

@@ -134,8 +134,11 @@
</div>
</div>
<div class="section" d="wifiSection" style="display: none;">
<div class="section" id="wifiSection">
<h2>📡 WLAN-Konfiguration</h2>
<div id="wifiRestrictionNotice" class="restriction-notice" style="display: none;">
🔒 WLAN-Konfiguration ist nur mit Lizenz Level 3 oder höher verfügbar. Aktuelle Lizenz: Level <span id="currentLicenseLevel">0</span>
</div>
<form id="wifiForm">
<div class="form-group">
<label for="wifi-ssid">WLAN Name (SSID):</label>
@@ -157,7 +160,7 @@
/>
</div>
<div class="button-group">
<button type="submit" class="btn btn-primary">
<button type="submit" id="wifiSubmitBtn" class="btn btn-primary">
💾 WLAN-Einstellungen speichern
</button>
</div>
@@ -413,6 +416,7 @@
// Check license level and update OTA button accordingly
updateOTAButtonAccess(data.tier || 0);
updateWifiButtonAccess(data.tier || 0)
})
.catch((error) => console.log("Info konnte nicht geladen werden"));
}
@@ -501,8 +505,8 @@
// Update OTA button access based on license level
function updateOTAButtonAccess(licenseLevel) {
const otaButton = document.getElementById("otaUpdateBtn");
const restrictionNotice = document.getElementById("otaRestrictionNotice");
const currentLevelSpan = document.getElementById("currentLicenseLevel");
const otarestrictionNotice = document.getElementById("otaRestrictionNotice");
const otacurrentLevelSpan = document.getElementById("currentLicenseLevel");
const level = parseInt(licenseLevel) || 0;
@@ -510,16 +514,44 @@
// License level 2 or higher - enable OTA
otaButton.classList.remove("btn-disabled");
otaButton.disabled = false;
restrictionNotice.style.display = "none";
otarestrictionNotice.style.display = "none";
} else {
// License level below 2 - disable OTA
otaButton.classList.add("btn-disabled");
otaButton.disabled = true;
restrictionNotice.style.display = "block";
currentLevelSpan.textContent = level;
otarestrictionNotice.style.display = "block";
otacurrentLevelSpan.textContent = level;
}
}
function updateWifiButtonAccess(licenseLevel) {
const wifiSubmitBtn = document.getElementById("wifiSubmitBtn");
const wifiForm = document.getElementById("wifiForm");
const wifiRestrictionNotice = document.getElementById("wifiRestrictionNotice");
const wifiCurrentLevelSpan = document.getElementById("currentLicenseLevel");
const level = parseInt(licenseLevel) || 0;
if (level >= 3) {
// License level 3 or higher - enable form
wifiSubmitBtn.classList.remove("btn-disabled");
wifiSubmitBtn.disabled = false;
wifiForm.querySelectorAll('input').forEach(input => {
input.disabled = false;
});
wifiRestrictionNotice.style.display = "none";
} else {
// License level below 3 - disable form
wifiSubmitBtn.classList.add("btn-disabled");
wifiSubmitBtn.disabled = true;
wifiForm.querySelectorAll('input').forEach(input => {
input.disabled = true;
});
wifiRestrictionNotice.style.display = "block";
wifiCurrentLevelSpan.textContent = level;
}
}
// OTA Update function with license check
function performOTAUpdate() {
const otaButton = document.getElementById("otaUpdateBtn");

View File

@@ -20,6 +20,11 @@ lib_compat_mode = strict
[env:wemos_d1_mini32]
board = wemos_d1_mini32
monitor_speed = 115200
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
targets = uploadfs
board_build.psram = disabled
lib_deps =
bblanchon/ArduinoJson@^7.4.1
esp32async/ESPAsyncWebServer@^3.7.7

99
src/buttonassigh.h Normal file
View File

@@ -0,0 +1,99 @@
#pragma once
#include <Arduino.h>
#include "master.h"
// Aquacross Timer - ESP32 Master (Webserver + ESP-NOW + Anlernmodus)
#include <ESPAsyncWebServer.h>
void handleLearningMode(const uint8_t* mac) {
// Prüfen ob MAC bereits einem anderen Button zugewiesen ist
if (buttonConfigs.start1.isAssigned && memcmp(buttonConfigs.start1.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.stop1.isAssigned && memcmp(buttonConfigs.stop1.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.start2.isAssigned && memcmp(buttonConfigs.start2.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.stop2.isAssigned && memcmp(buttonConfigs.stop2.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
// MAC ist noch nicht zugewiesen, normal fortfahren
switch(learningStep) {
case 0: // Start1
memcpy(buttonConfigs.start1.mac, mac, 6);
buttonConfigs.start1.isAssigned = true;
Serial.println("Start1 Button zugewiesen");
break;
case 1: // Stop1
memcpy(buttonConfigs.stop1.mac, mac, 6);
buttonConfigs.stop1.isAssigned = true;
Serial.println("Stop1 Button zugewiesen");
break;
case 2: // Start2
memcpy(buttonConfigs.start2.mac, mac, 6);
buttonConfigs.start2.isAssigned = true;
Serial.println("Start2 Button zugewiesen");
break;
case 3: // Stop2
memcpy(buttonConfigs.stop2.mac, mac, 6);
buttonConfigs.stop2.isAssigned = true;
Serial.println("Stop2 Button zugewiesen");
break;
}
learningStep++;
if (learningStep >= 4) {
learningMode = false;
learningStep = 0;
saveButtonConfig();
Serial.println("Lernmodus beendet!");
}
}
void handleStartLearning() {
learningMode = true;
// Count assigned buttons and set appropriate learning step
int assignedButtons = 0;
if (buttonConfigs.start1.isAssigned) assignedButtons++;
if (buttonConfigs.stop1.isAssigned) assignedButtons++;
if (buttonConfigs.start2.isAssigned) assignedButtons++;
if (buttonConfigs.stop2.isAssigned) assignedButtons++;
learningStep = assignedButtons;
Serial.printf("Learning mode started - %d buttons already assigned, continuing at step %d\n",
assignedButtons, learningStep);
}
void handleLearningStatus() {
DynamicJsonDocument doc(256);
doc["active"] = learningMode;
doc["step"] = learningStep;
String response;
serializeJson(doc, response);
}
void unlearnButton() {
memset(buttonConfigs.start1.mac, 0, 6);
buttonConfigs.start1.isAssigned = false;
memset(buttonConfigs.stop1.mac, 0, 6);
buttonConfigs.stop1.isAssigned = false;
memset(buttonConfigs.start2.mac, 0, 6);
buttonConfigs.start2.isAssigned = false;
memset(buttonConfigs.stop2.mac, 0, 6);
buttonConfigs.stop2.isAssigned = false;
saveButtonConfig();
Serial.println("Buttons wurden verlernt.");
}

View File

@@ -1,3 +1,4 @@
#pragma once
#include <Arduino.h>
#include "master.h"
#include <ArduinoJson.h>
@@ -6,6 +7,8 @@
#include <statusled.h>
#include "timesync.h"
#include "buttonassigh.h"
#include "helper.h"
// Datenstruktur für ESP-NOW Nachrichten
@@ -19,31 +22,110 @@ typedef struct {
} ButtonMessage;
PicoMQTT::Server mqtt;
PicoMQTT::ServerLocalSubscribe localsubscribe;
void readButtonJSON(const char * topic, const char * payload) {
if(strcmp(topic, "aquacross/button/press") == 0){
// Create a JSON document to parse the incoming message
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("JSON parsing failed: ");
Serial.println(error.c_str());
return;
}
// Extract values from JSON
int pressType = doc["type"] | 0;
const char* buttonId = doc["buttonmac"] | "unknown";
const char* messageId = doc["messageId"] | "unknown";
uint64_t timestamp = doc["timestamp"] | 0;
// Print received data
Serial.printf("Button Press Received:\n");
Serial.printf(" Type: %d\n", pressType);
Serial.printf(" Button MAC: %s\n", buttonId);
Serial.printf(" Message ID: %s\n", messageId);
Serial.printf(" Timestamp: %llu\n", timestamp);
auto macBytes = macStringToBytes(buttonId);
if (learningMode) {
handleLearningMode(macBytes.data());
return;
}
// Button-Zuordnung prüfen und entsprechende Aktion ausführen
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0 && (pressType == 2)) {
handleStart1();
} else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0 && (pressType == 2)) {
handleStop1();
} else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0 && (pressType == 2)) {
handleStart2();
} else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0 && (pressType == 2)) {
handleStop2();
}
// Flash status LED to indicate received message
updateStatusLED(3);
}
}
void setupMqttServer() {
// Set up the MQTT server with the desired port
// Subscribe to a topic pattern and attach a callback
mqtt.subscribe("#", [](const char * topic, const char * payload) {
Serial.printf("Received message in topic '%s': %s\n", topic, payload);
updateStatusLED(3); // Flash LED on message received
});
Serial.printf("Received message in topic '%s': %s\n", topic, payload);
readButtonJSON(topic, payload);
updateStatusLED(3); // Flash LED on message received
});
// Add the button subscription
// Start the MQTT server
mqtt.begin();
Serial.println("MQTT server started on port 1883");
}
void loopMqttServer() {
// Handle incoming MQTT messages
mqtt.loop();
// Optionally, you can publish a message periodically
static unsigned long lastPublish = 0;
if (millis() - lastPublish > 5000) { // Publish every 5 seconds
mqtt.publish("heartbeat/live", "Alive!");
lastPublish = millis();
}
mqtt.loop();
static unsigned long lastPublish = 0;
if (millis() - lastPublish > 30000) {
// Convert timestamp to string before publishing
char timeStr[32];
snprintf(timeStr, sizeof(timeStr), "%llu", getCurrentTimestampMs());
mqtt.publish("sync/time", timeStr);
lastPublish = millis();
}
}
void sendMQTTMessage(const char * topic, const char * message) {
// Publish a message to the specified topic
mqtt.publish(topic, message);
Serial.printf("Published message to topic '%s': %s\n", topic, message);
}
void sendMQTTJSONMessage(const char * topic, const JsonDocument & doc) {
String jsonString;
serializeJson(doc, jsonString);
// Publish the JSON string to the specified topic
auto publish = mqtt.begin_publish(topic, measureJson(doc));
serializeJson(doc, publish);
publish.send();
Serial.printf("Published JSON message to topic '%s': %s\n", topic, jsonString.c_str());
}

10
src/helper.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <Arduino.h>
#include "master.h"
std::array<uint8_t, 6> macStringToBytes(const char* macStr) {
std::array<uint8_t, 6> bytes;
sscanf(macStr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&bytes[0], &bytes[1], &bytes[2], &bytes[3], &bytes[4], &bytes[5]);
return bytes;
}

View File

@@ -24,100 +24,11 @@ const char* firmwareversion = "1.0.0"; // Version der Firmware
void handleLearningMode(const uint8_t* mac) {
// Prüfen ob MAC bereits einem anderen Button zugewiesen ist
if (buttonConfigs.start1.isAssigned && memcmp(buttonConfigs.start1.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.stop1.isAssigned && memcmp(buttonConfigs.stop1.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.start2.isAssigned && memcmp(buttonConfigs.start2.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
if (buttonConfigs.stop2.isAssigned && memcmp(buttonConfigs.stop2.mac, mac, 6) == 0) {
Serial.println("Diese MAC ist bereits zugewiesen - wird ignoriert");
return;
}
// MAC ist noch nicht zugewiesen, normal fortfahren
switch(learningStep) {
case 0: // Start1
memcpy(buttonConfigs.start1.mac, mac, 6);
buttonConfigs.start1.isAssigned = true;
Serial.println("Start1 Button zugewiesen");
break;
case 1: // Stop1
memcpy(buttonConfigs.stop1.mac, mac, 6);
buttonConfigs.stop1.isAssigned = true;
Serial.println("Stop1 Button zugewiesen");
break;
case 2: // Start2
memcpy(buttonConfigs.start2.mac, mac, 6);
buttonConfigs.start2.isAssigned = true;
Serial.println("Start2 Button zugewiesen");
break;
case 3: // Stop2
memcpy(buttonConfigs.stop2.mac, mac, 6);
buttonConfigs.stop2.isAssigned = true;
Serial.println("Stop2 Button zugewiesen");
break;
}
learningStep++;
if (learningStep >= 4) {
learningMode = false;
learningStep = 0;
saveButtonConfig();
Serial.println("Lernmodus beendet!");
}
}
void handleStartLearning() {
learningMode = true;
// Count assigned buttons and set appropriate learning step
int assignedButtons = 0;
if (buttonConfigs.start1.isAssigned) assignedButtons++;
if (buttonConfigs.stop1.isAssigned) assignedButtons++;
if (buttonConfigs.start2.isAssigned) assignedButtons++;
if (buttonConfigs.stop2.isAssigned) assignedButtons++;
learningStep = assignedButtons;
Serial.printf("Learning mode started - %d buttons already assigned, continuing at step %d\n",
assignedButtons, learningStep);
}
void handleLearningStatus() {
DynamicJsonDocument doc(256);
doc["active"] = learningMode;
doc["step"] = learningStep;
String response;
serializeJson(doc, response);
}
void unlearnButton() {
memset(buttonConfigs.start1.mac, 0, 6);
buttonConfigs.start1.isAssigned = false;
memset(buttonConfigs.stop1.mac, 0, 6);
buttonConfigs.stop1.isAssigned = false;
memset(buttonConfigs.start2.mac, 0, 6);
buttonConfigs.start2.isAssigned = false;
memset(buttonConfigs.stop2.mac, 0, 6);
buttonConfigs.stop2.isAssigned = false;
saveButtonConfig();
Serial.println("Buttons wurden verlernt.");
}
void handleStart1() {
if (!timerData.isRunning1) {
if (!timerData.isRunning1 && timerData.isReady1) {
timerData.isReady1 = false; // Setze auf "Nicht bereit" bis Stopp
timerData.startTime1 = millis();
timerData.isRunning1 = true;
timerData.endTime1 = 0;
@@ -141,7 +52,8 @@ void handleStop1() {
}
void handleStart2() {
if (!timerData.isRunning2) {
if (!timerData.isRunning2 && timerData.isReady2) {
timerData.isReady2 = false; // Setze auf "Nicht bereit" bis Stopp
timerData.startTime2 = millis();
timerData.isRunning2 = true;
timerData.endTime2 = 0;
@@ -185,6 +97,7 @@ void checkAutoReset() {
timerData.startTime1 = 0;
timerData.endTime1 = 0;
timerData.finishedSince1 = 0;
timerData.isReady1 = true; // Zurücksetzen auf "Bereit"
Serial.println("Bahn 1 automatisch auf 'Bereit' zurückgesetzt");
}
}
@@ -194,6 +107,7 @@ void checkAutoReset() {
timerData.startTime2 = 0;
timerData.endTime2 = 0;
timerData.finishedSince2 = 0;
timerData.isReady2 = true; // Zurücksetzen auf "Bereit"
Serial.println("Bahn 2 automatisch auf 'Bereit' zurückgesetzt");
}
}
@@ -321,12 +235,11 @@ void setup() {
setupRoutes();
setupLED();
setupMqttServer(); // MQTT Server initialisieren
}
void loop() {
checkAutoReset();
loopMqttServer(); // MQTT Server in der Loop aufrufen
delay(100);
}

View File

@@ -18,6 +18,8 @@ struct TimerData {
bool isRunning2 = false;
unsigned long finishedSince1 = 0;
unsigned long finishedSince2 = 0;
bool isReady1 = true; // Status für Bahn 1
bool isReady2 = true; // Status für Bahn 2
};
// Button Konfiguration

View File

@@ -196,3 +196,9 @@ bool isValidDateTime(int year, int month, int day, int hour, int minute, int sec
return day <= daysInMonth[month - 1];
}
uint64_t getCurrentTimestampMs() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000LL + (uint64_t)tv.tv_usec / 1000LL;
}

View File

@@ -5,6 +5,9 @@
#include <SPIFFS.h>
#include <esp_wifi.h>
#include <buttonassigh.h>
AsyncWebServer server(80);
void setupRoutes(){