first commit
This commit is contained in:
44
src/communication.h
Normal file
44
src/communication.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <Arduino.h>
|
||||
#include "master.h"
|
||||
|
||||
#include <PicoMQTT.h>
|
||||
|
||||
#include <statusled.h>
|
||||
#include "timesync.h"
|
||||
|
||||
|
||||
// Datenstruktur für ESP-NOW Nachrichten
|
||||
// Datenstruktur für ESP-NOW Nachrichten
|
||||
typedef struct {
|
||||
uint8_t messageType;
|
||||
uint8_t buttonId;
|
||||
int buttonPressed;
|
||||
uint32_t timestamp;
|
||||
char messageId[33]; // 32 hex chars + null terminator for 128-bit ID
|
||||
} ButtonMessage;
|
||||
|
||||
PicoMQTT::Server mqtt;
|
||||
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// Start the MQTT server
|
||||
mqtt.begin();
|
||||
}
|
||||
|
||||
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("test/topic", "Hello from ESP32!");
|
||||
lastPublish = millis();
|
||||
}
|
||||
}
|
||||
41
src/debug.h
Normal file
41
src/debug.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Zeit-bezogene Variablen und Includes
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <master.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
|
||||
void setupDebugAPI(AsyncWebServer& server);
|
||||
|
||||
|
||||
void setupDebugAPI(AsyncWebServer& server) {
|
||||
|
||||
//DEBUG
|
||||
server.on("/api/debug/start1", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
handleStart1();
|
||||
request->send(200, "text/plain", "handleStart1() called");
|
||||
});
|
||||
|
||||
server.on("/api/debug/stop1", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
handleStop1();
|
||||
request->send(200, "text/plain", "handleStop1() called");
|
||||
});
|
||||
|
||||
server.on("/api/debug/start2", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
handleStart2();
|
||||
request->send(200, "text/plain", "handleStart2() called");
|
||||
});
|
||||
|
||||
server.on("/api/debug/stop2", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
handleStop2();
|
||||
request->send(200, "text/plain", "handleStop2() called");
|
||||
});
|
||||
|
||||
|
||||
Serial.println("Debug-API initialisiert");
|
||||
}
|
||||
//DEBUG END
|
||||
106
src/licenceing.h
Normal file
106
src/licenceing.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <master.h>
|
||||
#include <Preferences.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "mbedtls/md.h"
|
||||
|
||||
const char* secret = "542ff224606c61fb3024e22f76ef9ac8";
|
||||
|
||||
// Preferences für persistente Speicherung
|
||||
Preferences preferences;
|
||||
|
||||
String licence;
|
||||
|
||||
//Prototype für Funktionen
|
||||
String getUniqueDeviceID();
|
||||
String hmacSHA256(const String& key, const String& message);
|
||||
bool checkLicense(const String& deviceID, const String& licenseKey);
|
||||
void setupLicenceAPI(AsyncWebServer& server);
|
||||
void saveLicenceToPrefs();
|
||||
void loadLicenceFromPrefs();
|
||||
|
||||
|
||||
String getUniqueDeviceID() {
|
||||
uint8_t mac[6];
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac); // Use STA MAC for uniqueness
|
||||
char id[13];
|
||||
sprintf(id, "%02X%02X%02X%02X%02X%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return String(id);
|
||||
}
|
||||
|
||||
String hmacSHA256(const String& key, const String& message) {
|
||||
byte hmacResult[32];
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
|
||||
mbedtls_md_init(&ctx);
|
||||
const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(md_type);
|
||||
mbedtls_md_setup(&ctx, md_info, 1);
|
||||
mbedtls_md_hmac_starts(&ctx, (const unsigned char*)key.c_str(), key.length());
|
||||
mbedtls_md_hmac_update(&ctx, (const unsigned char*)message.c_str(), message.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult);
|
||||
mbedtls_md_free(&ctx);
|
||||
|
||||
String result = "";
|
||||
for (int i = 0; i < 32; i++) {
|
||||
char buf[3];
|
||||
sprintf(buf, "%02X", hmacResult[i]);
|
||||
result += buf;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int getLicenseTier(const String& deviceID, const String& licenseKey) {
|
||||
for (int tier = 1; tier <= 4; ++tier) {
|
||||
String data = deviceID + ":" + String(tier);
|
||||
String expected = hmacSHA256(secret, data);
|
||||
if (licenseKey.equalsIgnoreCase(expected)) {
|
||||
return tier; // Found matching tier
|
||||
}
|
||||
}
|
||||
return 0; // No valid tier found
|
||||
}
|
||||
|
||||
void setupLicenceAPI(AsyncWebServer& server) {
|
||||
|
||||
server.on("/api/get-licence", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Received request to get licence");
|
||||
loadLicenceFromPrefs();
|
||||
String deviceID = getUniqueDeviceID();
|
||||
int tier = getLicenseTier(deviceID, licence);
|
||||
String json = "{\"licence\":\"" + licence + "\","
|
||||
"\"valid\":" + String(tier > 0 ? "true" : "false") +
|
||||
",\"tier\":" + String(tier) + "}";
|
||||
request->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
server.on("/api/set-licence", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Received request to set licence");
|
||||
if (request->hasParam("licence", true)) {
|
||||
licence = request->getParam("licence", true)->value();
|
||||
Serial.println("Received request to set licence " + licence);
|
||||
saveLicenceToPrefs(); // eigene Funktion
|
||||
request->send(200, "application/json", "{\"success\":true}");
|
||||
} else {
|
||||
request->send(400, "application/json", "{\"success\":false}");
|
||||
}
|
||||
});
|
||||
|
||||
Serial.println("Licence API setup complete");
|
||||
}
|
||||
|
||||
void saveLicenceToPrefs() {
|
||||
preferences.begin("key", false);
|
||||
preferences.putString("key", licence);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void loadLicenceFromPrefs() {
|
||||
preferences.begin("key", true);
|
||||
licence = preferences.getString("key", "");
|
||||
preferences.end();
|
||||
}
|
||||
331
src/master.cpp
Normal file
331
src/master.cpp
Normal file
@@ -0,0 +1,331 @@
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "master.h"
|
||||
// Aquacross Timer - ESP32 Master (Webserver + ESP-NOW + Anlernmodus)
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <esp_now.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Preferences.h>
|
||||
#include <PrettyOTA.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <AsyncTCP.h>
|
||||
|
||||
#include <timesync.h>
|
||||
#include <licenceing.h>
|
||||
#include <debug.h>
|
||||
#include <wificlass.h>
|
||||
#include <webserverrouter.h>
|
||||
#include <communication.h>
|
||||
|
||||
|
||||
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) {
|
||||
timerData.startTime1 = millis();
|
||||
timerData.isRunning1 = true;
|
||||
timerData.endTime1 = 0;
|
||||
Serial.println("Bahn 1 gestartet");
|
||||
}
|
||||
}
|
||||
|
||||
void handleStop1() {
|
||||
if (timerData.isRunning1) {
|
||||
timerData.endTime1 = millis();
|
||||
timerData.isRunning1 = false;
|
||||
unsigned long currentTime = timerData.endTime1 - timerData.startTime1;
|
||||
|
||||
if (timerData.bestTime1 == 0 || currentTime < timerData.bestTime1) {
|
||||
timerData.bestTime1 = currentTime;
|
||||
saveBestTimes();
|
||||
}
|
||||
timerData.finishedSince1 = millis();
|
||||
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime/1000.0) + "s");
|
||||
}
|
||||
}
|
||||
|
||||
void handleStart2() {
|
||||
if (!timerData.isRunning2) {
|
||||
timerData.startTime2 = millis();
|
||||
timerData.isRunning2 = true;
|
||||
timerData.endTime2 = 0;
|
||||
Serial.println("Bahn 2 gestartet");
|
||||
}
|
||||
}
|
||||
|
||||
void handleStop2() {
|
||||
if (timerData.isRunning2) {
|
||||
timerData.endTime2 = millis();
|
||||
timerData.isRunning2 = false;
|
||||
unsigned long currentTime = timerData.endTime2 - timerData.startTime2;
|
||||
|
||||
if (timerData.bestTime2 == 0 || currentTime < timerData.bestTime2) {
|
||||
timerData.bestTime2 = currentTime;
|
||||
saveBestTimes();
|
||||
}
|
||||
timerData.finishedSince2 = millis();
|
||||
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime/1000.0) + "s");
|
||||
}
|
||||
}
|
||||
|
||||
void checkAutoReset() {
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
if (timerData.isRunning1 && (currentTime - timerData.startTime1 > maxTimeBeforeReset)) {
|
||||
timerData.isRunning1 = false;
|
||||
timerData.startTime1 = 0;
|
||||
Serial.println("Bahn 1 automatisch zurückgesetzt");
|
||||
}
|
||||
|
||||
if (timerData.isRunning2 && (currentTime - timerData.startTime2 > maxTimeBeforeReset)) {
|
||||
timerData.isRunning2 = false;
|
||||
timerData.startTime2 = 0;
|
||||
Serial.println("Bahn 2 automatisch zurückgesetzt");
|
||||
}
|
||||
|
||||
// Automatischer Reset nach 10 Sekunden "Beendet"
|
||||
if (!timerData.isRunning1 && timerData.endTime1 > 0 && timerData.finishedSince1 > 0) {
|
||||
if (currentTime - timerData.finishedSince1 > maxTimeDisplay) {
|
||||
timerData.startTime1 = 0;
|
||||
timerData.endTime1 = 0;
|
||||
timerData.finishedSince1 = 0;
|
||||
Serial.println("Bahn 1 automatisch auf 'Bereit' zurückgesetzt");
|
||||
}
|
||||
}
|
||||
|
||||
if (!timerData.isRunning2 && timerData.endTime2 > 0 && timerData.finishedSince2 > 0) {
|
||||
if (currentTime - timerData.finishedSince2 > maxTimeDisplay) {
|
||||
timerData.startTime2 = 0;
|
||||
timerData.endTime2 = 0;
|
||||
timerData.finishedSince2 = 0;
|
||||
Serial.println("Bahn 2 automatisch auf 'Bereit' zurückgesetzt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void saveButtonConfig() {
|
||||
preferences.begin("buttons", false);
|
||||
preferences.putBytes("config", &buttonConfigs, sizeof(buttonConfigs));
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void loadButtonConfig() {
|
||||
preferences.begin("buttons", true);
|
||||
size_t schLen = preferences.getBytesLength("config");
|
||||
if (schLen == sizeof(buttonConfigs)) {
|
||||
preferences.getBytes("config", &buttonConfigs, schLen);
|
||||
}
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void saveBestTimes() {
|
||||
preferences.begin("times", false);
|
||||
preferences.putULong("best1", timerData.bestTime1);
|
||||
preferences.putULong("best2", timerData.bestTime2);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void loadBestTimes() {
|
||||
preferences.begin("times", true);
|
||||
timerData.bestTime1 = preferences.getULong("best1", 0);
|
||||
timerData.bestTime2 = preferences.getULong("best2", 0);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void saveSettings() {
|
||||
preferences.begin("settings", false);
|
||||
preferences.putULong("maxTime", maxTimeBeforeReset);
|
||||
preferences.putULong("maxTimeDisplay", maxTimeDisplay);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
void loadSettings() {
|
||||
preferences.begin("settings", true);
|
||||
maxTimeBeforeReset = preferences.getULong("maxTime", 300000);
|
||||
maxTimeDisplay = preferences.getULong("maxTimeDisplay", 20000);
|
||||
preferences.end();
|
||||
}
|
||||
|
||||
int checkLicence() {
|
||||
loadLicenceFromPrefs();
|
||||
String id = getUniqueDeviceID();
|
||||
int tier = getLicenseTier(id, licence); // licence = stored or entered key
|
||||
return tier;
|
||||
}
|
||||
|
||||
String getTimerDataJSON() {
|
||||
DynamicJsonDocument doc(1024);
|
||||
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
// Bahn 1
|
||||
if (timerData.isRunning1) {
|
||||
doc["time1"] = (currentTime - timerData.startTime1) / 1000.0;
|
||||
doc["status1"] = "running";
|
||||
} else if (timerData.endTime1 > 0) {
|
||||
doc["time1"] = (timerData.endTime1 - timerData.startTime1) / 1000.0;
|
||||
doc["status1"] = "finished";
|
||||
} else {
|
||||
doc["time1"] = 0;
|
||||
doc["status1"] = "ready";
|
||||
}
|
||||
|
||||
// Bahn 2
|
||||
if (timerData.isRunning2) {
|
||||
doc["time2"] = (currentTime - timerData.startTime2) / 1000.0;
|
||||
doc["status2"] = "running";
|
||||
} else if (timerData.endTime2 > 0) {
|
||||
doc["time2"] = (timerData.endTime2 - timerData.startTime2) / 1000.0;
|
||||
doc["status2"] = "finished";
|
||||
} else {
|
||||
doc["time2"] = 0;
|
||||
doc["status2"] = "ready";
|
||||
}
|
||||
|
||||
// Beste Zeiten
|
||||
doc["best1"] = timerData.bestTime1 / 1000.0;
|
||||
doc["best2"] = timerData.bestTime2 / 1000.0;
|
||||
|
||||
// Lernmodus
|
||||
doc["learningMode"] = learningMode;
|
||||
if (learningMode) {
|
||||
String buttons[] = {"Start Bahn 1", "Stop Bahn 1", "Start Bahn 2", "Stop Bahn 2"};
|
||||
doc["learningButton"] = buttons[learningStep];
|
||||
}
|
||||
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
if (!SPIFFS.begin(true)) {
|
||||
Serial.println("SPIFFS Mount Failed");
|
||||
return;
|
||||
}
|
||||
|
||||
//setup external libararies
|
||||
setupTimeAPI(server);
|
||||
setupLicenceAPI(server);
|
||||
setupDebugAPI(server);
|
||||
|
||||
|
||||
// Gespeicherte Daten laden
|
||||
loadButtonConfig();
|
||||
loadBestTimes();
|
||||
loadSettings();
|
||||
|
||||
setupWifi(); // WiFi initialisieren
|
||||
setupOTA(&server);
|
||||
|
||||
setupRoutes();
|
||||
|
||||
setupMqttServer(); // MQTT Server initialisieren
|
||||
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
checkAutoReset();
|
||||
loopMqttServer(); // MQTT Server in der Loop aufrufen
|
||||
delay(100);
|
||||
}
|
||||
64
src/master.h
Normal file
64
src/master.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
// Timer Struktur
|
||||
struct TimerData {
|
||||
unsigned long startTime1 = 0;
|
||||
unsigned long startTime2 = 0;
|
||||
unsigned long endTime1 = 0;
|
||||
unsigned long endTime2 = 0;
|
||||
unsigned long bestTime1 = 0;
|
||||
unsigned long bestTime2 = 0;
|
||||
bool isRunning1 = false;
|
||||
bool isRunning2 = false;
|
||||
unsigned long finishedSince1 = 0;
|
||||
unsigned long finishedSince2 = 0;
|
||||
};
|
||||
|
||||
// Button Konfiguration
|
||||
struct ButtonConfig {
|
||||
uint8_t mac[6];
|
||||
bool isAssigned = false;
|
||||
};
|
||||
|
||||
struct ButtonConfigs {
|
||||
ButtonConfig start1;
|
||||
ButtonConfig stop1;
|
||||
ButtonConfig start2;
|
||||
ButtonConfig stop2;
|
||||
};
|
||||
|
||||
extern const char* firmwareversion;
|
||||
|
||||
// Globale Variablen
|
||||
TimerData timerData;
|
||||
ButtonConfigs buttonConfigs;
|
||||
bool learningMode = false;
|
||||
int learningStep = 0; // 0=Start1, 1=Stop1, 2=Start2, 3=Stop2
|
||||
unsigned long maxTimeBeforeReset = 300000; // 5 Minuten default
|
||||
unsigned long maxTimeDisplay = 20000; // 20 Sekunden Standard (in ms)
|
||||
bool wifimodeAP = false; // AP-Modus deaktiviert
|
||||
|
||||
//Function Declarations
|
||||
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len);
|
||||
void handleLearningMode(const uint8_t* mac);
|
||||
void handleStartLearning();
|
||||
void handleStart1();
|
||||
void handleStop1();
|
||||
void handleStart2();
|
||||
void handleStop2();
|
||||
void checkAutoReset();
|
||||
void saveButtonConfig();
|
||||
void loadButtonConfig();
|
||||
void saveBestTimes();
|
||||
void loadBestTimes();
|
||||
void saveSettings();
|
||||
void loadSettings();
|
||||
void unlearnButton();
|
||||
int checkLicence();
|
||||
String getTimerDataJSON();
|
||||
54
src/statusled.h
Normal file
54
src/statusled.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
#define LED_PIN LED_BUILTIN
|
||||
|
||||
// Status LED
|
||||
unsigned long lastLedBlink = 0;
|
||||
bool ledState = false;
|
||||
|
||||
|
||||
void updateStatusLED(int blinkPattern) {
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
switch (blinkPattern) {
|
||||
case 0: // Suche Master - Langsames Blinken
|
||||
if (currentTime - lastLedBlink > 1000) {
|
||||
ledState = !ledState;
|
||||
digitalWrite(LED_PIN, ledState);
|
||||
lastLedBlink = currentTime;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // Verbunden - Kurzes Blinken alle 3 Sekunden
|
||||
if (currentTime - lastLedBlink > 3000) {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
lastLedBlink = currentTime;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Button gedrückt - Schnelles Blinken 3x
|
||||
static int blinkCount = 0;
|
||||
if (currentTime - lastLedBlink > 100) {
|
||||
ledState = !ledState;
|
||||
digitalWrite(LED_PIN, ledState);
|
||||
lastLedBlink = currentTime;
|
||||
blinkCount++;
|
||||
|
||||
if (blinkCount >= 6) { // 3 komplette Blinks
|
||||
blinkCount = 0;
|
||||
blinkPattern = 1; // Zurück zu verbunden
|
||||
}
|
||||
}
|
||||
|
||||
case 3: // Flash bei Empfang - Einmaliges kurzes Blinken
|
||||
{
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
198
src/timesync.h
Normal file
198
src/timesync.h
Normal file
@@ -0,0 +1,198 @@
|
||||
// Zeit-bezogene Variablen und Includes
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
// Globale Zeitvariablen
|
||||
struct timeval tv;
|
||||
struct timezone tz;
|
||||
time_t now;
|
||||
struct tm timeinfo;
|
||||
|
||||
void setupTimeAPI(AsyncWebServer& server);
|
||||
String getCurrentTimeJSON();
|
||||
bool setSystemTime(long timestamp);
|
||||
|
||||
// Hilfsfunktionen für Zeit-Management
|
||||
String getCurrentTimeJSON() {
|
||||
gettimeofday(&tv, &tz);
|
||||
now = tv.tv_sec;
|
||||
|
||||
StaticJsonDocument<200> doc;
|
||||
doc["timestamp"] = (long)now;
|
||||
doc["success"] = true;
|
||||
|
||||
// Zusätzliche Zeitinformationen
|
||||
gmtime_r(&now, &timeinfo);
|
||||
char timeStr[64];
|
||||
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||||
doc["formatted"] = String(timeStr);
|
||||
doc["year"] = timeinfo.tm_year + 1900;
|
||||
doc["month"] = timeinfo.tm_mon + 1;
|
||||
doc["day"] = timeinfo.tm_mday;
|
||||
doc["hour"] = timeinfo.tm_hour;
|
||||
doc["minute"] = timeinfo.tm_min;
|
||||
doc["second"] = timeinfo.tm_sec;
|
||||
|
||||
String response;
|
||||
serializeJson(doc, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
bool setSystemTime(long timestamp) {
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timestamp;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
if (settimeofday(&tv, NULL) == 0) {
|
||||
Serial.println("Zeit erfolgreich gesetzt: " + String(timestamp));
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Fehler beim Setzen der Zeit");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void setupTimeAPI(AsyncWebServer& server) {
|
||||
|
||||
// API-Endpunkt: Aktuelle Zeit abrufen
|
||||
server.on("/api/time", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
String response = getCurrentTimeJSON();
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
|
||||
// API-Endpunkt: Zeit setzen
|
||||
server.on("/api/set-time", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
StaticJsonDocument<100> doc;
|
||||
|
||||
if (request->hasParam("timestamp", true)) {
|
||||
String timestampStr = request->getParam("timestamp", true)->value();
|
||||
long timestamp = timestampStr.toInt();
|
||||
|
||||
if (timestamp > 0) {
|
||||
bool success = setSystemTime(timestamp);
|
||||
|
||||
doc["success"] = success;
|
||||
if (success) {
|
||||
doc["message"] = "Zeit erfolgreich gesetzt";
|
||||
doc["timestamp"] = timestamp;
|
||||
} else {
|
||||
doc["message"] = "Fehler beim Setzen der Zeit";
|
||||
}
|
||||
} else {
|
||||
doc["success"] = false;
|
||||
doc["message"] = "Ungültiger Timestamp";
|
||||
}
|
||||
} else {
|
||||
doc["success"] = false;
|
||||
doc["message"] = "Timestamp-Parameter fehlt";
|
||||
}
|
||||
|
||||
String response;
|
||||
serializeJson(doc, response);
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
|
||||
// Alternative Implementierung für manuelle Datum/Zeit-Eingabe
|
||||
server.on("/api/set-datetime", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
StaticJsonDocument<150> doc;
|
||||
|
||||
if (request->hasParam("year", true) &&
|
||||
request->hasParam("month", true) &&
|
||||
request->hasParam("day", true) &&
|
||||
request->hasParam("hour", true) &&
|
||||
request->hasParam("minute", true) &&
|
||||
request->hasParam("second", true)) {
|
||||
|
||||
struct tm timeinfo;
|
||||
timeinfo.tm_year = request->getParam("year", true)->value().toInt() - 1900;
|
||||
timeinfo.tm_mon = request->getParam("month", true)->value().toInt() - 1;
|
||||
timeinfo.tm_mday = request->getParam("day", true)->value().toInt();
|
||||
timeinfo.tm_hour = request->getParam("hour", true)->value().toInt();
|
||||
timeinfo.tm_min = request->getParam("minute", true)->value().toInt();
|
||||
timeinfo.tm_sec = request->getParam("second", true)->value().toInt();
|
||||
|
||||
time_t timestamp = mktime(&timeinfo);
|
||||
|
||||
if (timestamp != -1) {
|
||||
bool success = setSystemTime(timestamp);
|
||||
|
||||
doc["success"] = success;
|
||||
if (success) {
|
||||
doc["message"] = "Zeit erfolgreich gesetzt";
|
||||
doc["timestamp"] = (long)timestamp;
|
||||
} else {
|
||||
doc["message"] = "Fehler beim Setzen der Zeit";
|
||||
}
|
||||
} else {
|
||||
doc["success"] = false;
|
||||
doc["message"] = "Ungültiges Datum/Zeit";
|
||||
}
|
||||
} else {
|
||||
doc["success"] = false;
|
||||
doc["message"] = "Datum/Zeit-Parameter fehlen";
|
||||
}
|
||||
|
||||
String response;
|
||||
serializeJson(doc, response);
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
|
||||
// Erweiterte Zeit-Informationen (optional)
|
||||
server.on("/api/time/info", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
gettimeofday(&tv, &tz);
|
||||
now = tv.tv_sec;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
|
||||
StaticJsonDocument<400> doc;
|
||||
doc["timestamp"] = (long)now;
|
||||
doc["uptime"] = millis();
|
||||
|
||||
// Formatierte Zeitstrings
|
||||
char buffer[64];
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d", &timeinfo);
|
||||
doc["date"] = String(buffer);
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%H:%M:%S", &timeinfo);
|
||||
doc["time"] = String(buffer);
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%A", &timeinfo);
|
||||
doc["weekday"] = String(buffer);
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%B", &timeinfo);
|
||||
doc["month_name"] = String(buffer);
|
||||
|
||||
// Zusätzliche Infos
|
||||
doc["day_of_year"] = timeinfo.tm_yday + 1;
|
||||
doc["week_of_year"] = (timeinfo.tm_yday + 7 - timeinfo.tm_wday) / 7;
|
||||
doc["is_dst"] = timeinfo.tm_isdst;
|
||||
|
||||
String response;
|
||||
serializeJson(doc, response);
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
Serial.println("Zeit-API initialisiert");
|
||||
}
|
||||
|
||||
// Hilfsfunktion: Zeit-Validierung
|
||||
bool isValidDateTime(int year, int month, int day, int hour, int minute, int second) {
|
||||
if (year < 2020 || year > 2099) return false;
|
||||
if (month < 1 || month > 12) return false;
|
||||
if (day < 1 || day > 31) return false;
|
||||
if (hour < 0 || hour > 23) return false;
|
||||
if (minute < 0 || minute > 59) return false;
|
||||
if (second < 0 || second > 59) return false;
|
||||
|
||||
// Erweiterte Validierung für Monatstage
|
||||
int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
// Schaltjahr-Prüfung
|
||||
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {
|
||||
daysInMonth[1] = 29;
|
||||
}
|
||||
|
||||
return day <= daysInMonth[month - 1];
|
||||
}
|
||||
169
src/webserverrouter.h
Normal file
169
src/webserverrouter.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#include <Arduino.h>
|
||||
#include "master.h"
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
void setupRoutes(){
|
||||
// Web Server Routes
|
||||
|
||||
// SPIFFS initialisieren
|
||||
|
||||
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(SPIFFS, "/index.html", "text/html");
|
||||
});
|
||||
|
||||
server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(SPIFFS, "/settings.html", "text/html");
|
||||
});
|
||||
|
||||
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(SPIFFS, "/about.html", "text/html");
|
||||
});
|
||||
|
||||
server.on("/api/data", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(200, "application/json", getTimerDataJSON());
|
||||
});
|
||||
|
||||
server.on("/api/reset-best", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/reset-best called");
|
||||
timerData.bestTime1 = 0;
|
||||
timerData.bestTime2 = 0;
|
||||
saveBestTimes();
|
||||
DynamicJsonDocument doc(64);
|
||||
doc["success"] = true;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
server.on("/api/unlearn-button", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/unlearn-button called");
|
||||
unlearnButton();
|
||||
request->send(200, "application/json", "{\"success\":true}");
|
||||
|
||||
});
|
||||
|
||||
|
||||
server.on("/api/set-max-time", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/set-max-time called");
|
||||
bool changed = false;
|
||||
if (request->hasParam("maxTime", true)) {
|
||||
maxTimeBeforeReset = request->getParam("maxTime", true)->value().toInt() * 1000;
|
||||
changed = true;
|
||||
}
|
||||
if (request->hasParam("maxTimeDisplay", true)) {
|
||||
maxTimeDisplay = request->getParam("maxTimeDisplay", true)->value().toInt() * 1000;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
saveSettings();
|
||||
DynamicJsonDocument doc(32);
|
||||
doc["success"] = true;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
request->send(200, "application/json", result);
|
||||
} else {
|
||||
request->send(400, "application/json", "{\"success\":false}");
|
||||
}
|
||||
});
|
||||
|
||||
server.on("/api/get-settings", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/get-settings called");
|
||||
DynamicJsonDocument doc(256);
|
||||
doc["maxTime"] = maxTimeBeforeReset / 1000;
|
||||
doc["maxTimeDisplay"] = maxTimeDisplay / 1000;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
server.on("/api/start-learning", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/start-learning called");
|
||||
learningMode = true;
|
||||
learningStep = 0;
|
||||
DynamicJsonDocument doc(64);
|
||||
doc["success"] = true;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
Serial.println("Learning mode started");
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
server.on("/api/stop-learning", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
Serial.println("/api/stop-learning called");
|
||||
learningMode = false;
|
||||
learningStep = 0;
|
||||
DynamicJsonDocument doc(64);
|
||||
doc["success"] = true;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
Serial.println("Learning mode stopped");
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
server.on("/api/learn/status", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
DynamicJsonDocument doc(256);
|
||||
doc["active"] = learningMode;
|
||||
doc["step"] = learningStep;
|
||||
String response;
|
||||
serializeJson(doc, response);
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
|
||||
server.on("/api/buttons/status", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
DynamicJsonDocument doc(128);
|
||||
doc["lane1Start"] = buttonConfigs.start1.isAssigned;
|
||||
doc["lane1Stop"] = buttonConfigs.stop1.isAssigned;
|
||||
doc["lane2Start"] = buttonConfigs.start2.isAssigned;
|
||||
doc["lane2Stop"] = buttonConfigs.stop2.isAssigned;
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
server.on("/api/info", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
DynamicJsonDocument doc(256);
|
||||
|
||||
// IP address
|
||||
IPAddress ip = WiFi.softAPIP();
|
||||
doc["ip"] = ip.toString();
|
||||
doc["channel"] = WiFi.channel();
|
||||
|
||||
// MAC address
|
||||
uint8_t mac[6];
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||||
char macStr[18];
|
||||
sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
doc["mac"] = macStr;
|
||||
|
||||
// Free memory
|
||||
doc["freeMemory"] = ESP.getFreeHeap();
|
||||
|
||||
// Connected buttons (count assigned)
|
||||
int connected = 0;
|
||||
if (buttonConfigs.start1.isAssigned) connected++;
|
||||
if (buttonConfigs.stop1.isAssigned) connected++;
|
||||
if (buttonConfigs.start2.isAssigned) connected++;
|
||||
if (buttonConfigs.stop2.isAssigned) connected++;
|
||||
doc["connectedButtons"] = connected;
|
||||
|
||||
doc["valid"] = checkLicence() > 0 ? "Ja" : "Nein";
|
||||
doc["tier"] = checkLicence() ;
|
||||
|
||||
|
||||
String result;
|
||||
serializeJson(doc, result);
|
||||
request->send(200, "application/json", result);
|
||||
});
|
||||
|
||||
// Statische Dateien
|
||||
server.serveStatic("/", SPIFFS, "/");
|
||||
server.begin();
|
||||
Serial.println("Web Server gestartet");
|
||||
|
||||
}
|
||||
59
src/wificlass.h
Normal file
59
src/wificlass.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <PrettyOTA.h>
|
||||
#include <esp_now.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "master.h"
|
||||
#include "licenceing.h"
|
||||
|
||||
const char* ssidAP = "AquaCross-Timer";
|
||||
const char* passwordAP = "aquacross123";
|
||||
|
||||
const char* ssidSTA = "Obiwlankenobi";
|
||||
const char* passwordSTA = "Delfine1!";
|
||||
|
||||
PrettyOTA OTAUpdates;
|
||||
|
||||
|
||||
void setupWifi() {
|
||||
|
||||
WiFi.mode(WIFI_MODE_APSTA);
|
||||
WiFi.softAP(ssidAP, passwordAP);
|
||||
WiFi.begin(ssidSTA, passwordSTA);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.println("Verbunden mit WLAN!");
|
||||
Serial.print("IP-Adresse: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
Serial.println("WiFi AP gestartet");
|
||||
Serial.print("IP Adresse: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
Serial.println("PrettyOTA can be accessed at: http://" + WiFi.softAPIP().toString() + "/update");
|
||||
|
||||
}
|
||||
|
||||
void setupOTA(AsyncWebServer *server) {
|
||||
// Initialize PrettyOTA
|
||||
OTAUpdates.Begin(server);
|
||||
|
||||
// Set unique Hardware-ID for your hardware/board
|
||||
OTAUpdates.SetHardwareID("AquaCross-Master");
|
||||
|
||||
// Set firmware version to 1.0.0
|
||||
OTAUpdates.SetAppVersion(firmwareversion);
|
||||
|
||||
// Set current build time and date
|
||||
PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// WiFi als Access Point
|
||||
|
||||
Reference in New Issue
Block a user