diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini index eceffa0..8b5ef10 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,34 +1,38 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - [env:esp32dev] platform = espressif32 board = esp32dev framework = arduino -# Libraries hier eintragen: +monitor_speed = 115200 +upload_speed = 921600 + +# Flash-Einstellungen für Stabilität +board_build.flash_mode = dio +board_build.flash_frequency = 80m +board_build.partitions = default.csv + +# Compiler Flags für Stabilität +build_flags = + -DBOARD_HAS_PSRAM=0 + -DCONFIG_ESP32_DEFAULT_CPU_FREQ_240=1 + -DCONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 + -DCONFIG_ESP_INT_WDT_TIMEOUT_MS=10000 + -DCONFIG_ESP_TASK_WDT_TIMEOUT_S=10 + -DCONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=1 + -DCONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=1 + -DARDUINO_LOOP_STACK_SIZE=16384 + -DCONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 + -DCONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 + -DCONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 + -DCONFIG_LWIP_TCP_MSS=1440 + -DCONFIG_LWIP_TCP_WND=32768 + +# Libraries lib_deps = - knolleary/PubSubClient @ ^2.8 - arduino-libraries/NTPClient @ ^3.2.1 - bblanchon/ArduinoJson @ ^6.21.3 -# SERIAL MONITOR EINSTELLUNGEN: -monitor_speed = 115200 # Baudrate -monitor_port = COM9 # Port festlegen -monitor_filters = # Filter aktivieren - colorize # Farbige Ausgabe - esp32_exception_decoder # ESP32 Fehler dekodieren - log2file # In Datei speichern - time # Zeitstempel - default # Standardfilter -monitor_rts = 0 # RTS deaktivieren -monitor_dtr = 0 # DTR deaktivieren -monitor_echo = yes # Eingaben anzeigen -monitor_raw = no # Raw-Mode -monitor_encoding = utf8 # Encoding -monitor_rx_timeout = 3 # Timeout in Sekunden + knolleary/PubSubClient@2.8 + bblanchon/ArduinoJson@6.21.3 + arduino-libraries/NTPClient@3.2.1 + +# Monitor Settings +monitor_filters = + time + esp32_exception_decoder \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 681cb95..c9dc68c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,181 +4,125 @@ #include #include #include -#include +#include // Watchdog +#include // WiFi-Management +#include // Speicher-Überwachung -// =================== HARDWARE KONFIGURATION =================== -// WICHTIG: Dein KY-025 ist INVERTIERT (HIGH bei Magnet, LOW ohne Magnet) -#define REED_PIN 4 // GPIO4 (funktioniert gut mit Pullup) -const float PULSES_PER_M3 = 100.0; // Anpassen: Wie viele Impulse pro m³? +// =================== HARDWARE =================== +#define REED_PIN 4 +const float PULSES_PER_M3 = 100.0; +const unsigned long DEBOUNCE_MS = 50; +const unsigned long MIN_PULSE_GAP = 200; // Auf 200ms erhöht! -// =================== NETZWERK KONFIGURATION =================== +// =================== NETZWERK =================== const char* SSID = "pipanet"; const char* PASS = "passatvr6"; - -// =================== MQTT KONFIGURATION =================== const char* MQTT_SERVER = "192.168.2.173"; -const char* STATE_TOPIC = "homeassistant/sensor/gaszahler/state"; -const char* COMMAND_TOPIC = "homeassistant/sensor/gaszahler/set"; -const char* CONFIG_TOPIC = "homeassistant/sensor/gaszahler/config"; -const char* AVAILABILITY_TOPIC = "homeassistant/sensor/gaszahler/status"; +const int MQTT_PORT = 1883; + +// =================== INTERVALL ZEITEN =================== +const unsigned long MQTT_INTERVAL = 60000; // 1 Minute statt 30 Sekunden +const unsigned long WIFI_CHECK_INTERVAL = 300000; // 5 Minuten +const unsigned long NTP_INTERVAL = 3600000; // 1 Stunde +const unsigned long STATUS_INTERVAL = 300000; // 5 Minuten // =================== GLOBALE VARIABLEN =================== -// volatile für Interrupt-Sicherheit volatile unsigned long totalPulses = 0; volatile unsigned long dailyPulses = 0; -volatile bool lastReedState = false; volatile unsigned long lastPulseTime = 0; -volatile bool pulseFlag = false; // Für sichere Hauptschleife-Verarbeitung +volatile bool pulseFlag = false; +volatile bool lastReedState = HIGH; -const unsigned long DEBOUNCE_MS = 50; // Entprellzeit -const unsigned long MIN_PULSE_GAP = 100; // Minimaler Impulsabstand (ms) -const int EEPROM_SIZE = 128; +unsigned long lastMQTT = 0; +unsigned long lastWiFiCheck = 0; +unsigned long lastNTP = 0; +unsigned long lastStatus = 0; -// =================== OBJEKTE =================== +// Objekte WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 60000); // UTC+1, Update alle 60s +NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 60000); WiFiClient wifiClient; PubSubClient mqtt(wifiClient); -// =================== EEPROM FUNKTIONEN =================== -void saveToEEPROM() { - EEPROM.begin(EEPROM_SIZE); - - // Magic Number zum Erkennen von gültigen Daten - EEPROM.put(0, 0xABCD1234); - - // Daten speichern - EEPROM.put(4, totalPulses); - EEPROM.put(8, dailyPulses); - - // CRC-Prüfsumme - uint32_t crc = totalPulses ^ dailyPulses ^ 0x55AA55AA; - EEPROM.put(12, crc); - - EEPROM.commit(); - EEPROM.end(); - - Serial.printf("EEPROM gespeichert: Total=%lu, Daily=%lu\n", totalPulses, dailyPulses); -} - -void loadFromEEPROM() { - EEPROM.begin(EEPROM_SIZE); - - // Magic Number prüfen - uint32_t magic; - EEPROM.get(0, magic); - - if (magic == 0xABCD1234) { - // Daten laden - EEPROM.get(4, totalPulses); - EEPROM.get(8, dailyPulses); - - // CRC prüfen - uint32_t storedCRC, calculatedCRC; - EEPROM.get(12, storedCRC); - calculatedCRC = totalPulses ^ dailyPulses ^ 0x55AA55AA; - - if (storedCRC == calculatedCRC) { - Serial.printf("EEPROM geladen: Total=%lu (%.2f m³), Daily=%lu (%.2f m³)\n", - totalPulses, totalPulses / PULSES_PER_M3, - dailyPulses, dailyPulses / PULSES_PER_M3); - } else { - Serial.println("EEPROM CRC Fehler - Reset auf 0"); - totalPulses = 0; - dailyPulses = 0; - } - } else { - Serial.println("Keine gültigen EEPROM-Daten gefunden"); - totalPulses = 0; - dailyPulses = 0; - } - - EEPROM.end(); -} - -// =================== INTERRUPT HANDLER =================== +// =================== INTERRUPT =================== void IRAM_ATTR handleReedInterrupt() { unsigned long now = millis(); - // Hardware-Entprellung und Mindestabstand - if (now - lastPulseTime < MIN_PULSE_GAP) { - return; // Zu schnelle Impulse ignorieren - } + if (now - lastPulseTime < MIN_PULSE_GAP) return; bool currentState = digitalRead(REED_PIN); - // WICHTIG: Dein Sensor ist INVERTIERT! - // HIGH = Magnet vorhanden (Zähler im Ruhezustand) - // LOW = Magnet weg (Zähler bewegt sich -> Impuls) - + // DEIN invertierter Sensor! if (currentState == LOW && lastReedState == HIGH) { - // Flanke von HIGH nach LOW = Magnet wurde entfernt = Zähler bewegt sich - pulseFlag = true; // Für Hauptschleife + pulseFlag = true; lastPulseTime = now; } lastReedState = currentState; } -// =================== MQTT FUNKTIONEN =================== -void sendHAConfig() { - StaticJsonDocument<512> config; +// =================== EEPROM =================== +void saveToEEPROM() { + EEPROM.begin(128); - // Gerätekonfiguration - JsonObject device = config.createNestedObject("device"); - device["identifiers"][0] = String("gasmeter_") + String((uint32_t)ESP.getEfuseMac(), HEX); - device["name"] = "Gas Zähler"; - device["manufacturer"] = "DIY"; - device["model"] = "ESP32 + KY-025"; - device["sw_version"] = "2.0"; + uint32_t magic = 0xABCD1234; + EEPROM.put(0, magic); + EEPROM.put(4, totalPulses); + EEPROM.put(8, dailyPulses); - // Totale Gasmenge - config["name"] = "Gas Verbrauch Total"; - config["unique_id"] = String("gasmeter_total_") + String((uint32_t)ESP.getEfuseMac(), HEX); - config["state_topic"] = STATE_TOPIC; - config["unit_of_meas"] = "m³"; - config["device_class"] = "gas"; - config["state_class"] = "total_increasing"; - config["value_template"] = "{{ value_json.total_m3 }}"; - config["availability_topic"] = AVAILABILITY_TOPIC; - config["payload_available"] = "online"; - config["payload_not_available"] = "offline"; - config["json_attributes_topic"] = STATE_TOPIC; + // CRC berechnen + uint32_t crc = 0; + for (int i = 0; i < 12; i++) { + crc ^= EEPROM.read(i); + } + EEPROM.put(12, crc); - char buffer[512]; - serializeJson(config, buffer); - mqtt.publish(CONFIG_TOPIC, buffer, true); - Serial.println("Home Assistant Config gesendet"); + EEPROM.commit(); + EEPROM.end(); } -void sendMQTTData() { - StaticJsonDocument<256> doc; +void loadFromEEPROM() { + EEPROM.begin(128); + + uint32_t magic; + EEPROM.get(0, magic); + + if (magic == 0xABCD1234) { + EEPROM.get(4, totalPulses); + EEPROM.get(8, dailyPulses); + + uint32_t storedCRC, calcCRC = 0; + EEPROM.get(12, storedCRC); + for (int i = 0; i < 12; i++) calcCRC ^= EEPROM.read(i); + + if (storedCRC != calcCRC) { + totalPulses = 0; + dailyPulses = 0; + } + } + EEPROM.end(); +} + +// =================== MQTT =================== +void sendMQTTData() { + if (!mqtt.connected()) return; + + StaticJsonDocument<384> doc; - // Berechnungen float total_m3 = totalPulses / PULSES_PER_M3; float daily_m3 = dailyPulses / PULSES_PER_M3; - float hourly_m3 = daily_m3 / 24.0; // Vereinfachte Annahme - // Hauptwerte doc["total_m3"] = total_m3; doc["daily_m3"] = daily_m3; - doc["hourly_m3"] = hourly_m3; doc["total_pulses"] = totalPulses; - - // Zusätzliche Info - doc["pulses_per_m3"] = PULSES_PER_M3; - doc["timestamp"] = timeClient.getFormattedTime(); - doc["uptime"] = millis() / 1000; doc["rssi"] = WiFi.RSSI(); + doc["uptime"] = millis() / 1000; + doc["heap"] = ESP.getFreeHeap(); - char output[256]; - serializeJson(doc, output); + char output[384]; + size_t n = serializeJson(doc, output); - if (mqtt.publish(STATE_TOPIC, output, true)) { - Serial.printf("MQTT gesendet: %.3f m³ total, %.3f m³ heute\n", total_m3, daily_m3); - } else { - Serial.println("MQTT Sendefehler!"); - } + mqtt.publish("homeassistant/sensor/gaszahler/state", output, true); } void mqttCallback(char* topic, byte* payload, unsigned int length) { @@ -186,216 +130,187 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { memcpy(message, payload, length); message[length] = '\0'; - Serial.printf("MQTT Callback: Topic=%s, Message=%s\n", topic, message); - - if (String(topic) == COMMAND_TOPIC) { + if (String(topic).endsWith("/set")) { if (strcmp(message, "reset_total") == 0) { totalPulses = 0; saveToEEPROM(); - Serial.println("Totalzähler zurückgesetzt"); - } - else if (strcmp(message, "reset_daily") == 0) { + } else if (strcmp(message, "reset_daily") == 0) { dailyPulses = 0; saveToEEPROM(); - Serial.println("Tageszähler zurückgesetzt"); - } - else if (strcmp(message, "restart") == 0) { - Serial.println("Neustart via MQTT..."); + } else if (strcmp(message, "restart") == 0) { ESP.restart(); } - - // Sofortige Bestätigung senden - sendMQTTData(); } } void reconnectMQTT() { static unsigned long lastAttempt = 0; - if (!mqtt.connected()) { - if (millis() - lastAttempt > 5000) { - Serial.print("Verbinde mit MQTT..."); - - // Client ID mit MAC-Adresse für Eindeutigkeit - String clientId = "GasMeter-" + String((uint32_t)ESP.getEfuseMac(), HEX); - - if (mqtt.connect(clientId.c_str(), AVAILABILITY_TOPIC, 1, true, "offline")) { - Serial.println("verbunden!"); - - // Verfügbarkeit melden - mqtt.publish(AVAILABILITY_TOPIC, "online", true); - - // Topics abonnieren - mqtt.subscribe(COMMAND_TOPIC); - - // Config an HA senden - sendHAConfig(); - - // Sofort Daten senden - sendMQTTData(); - } else { - Serial.printf("fehlgeschlagen, rc=%d\n", mqtt.state()); - } - lastAttempt = millis(); - } - } -} - -// =================== TAGESRESET =================== -void checkDailyReset() { - static int lastDay = -1; - - timeClient.update(); - int currentDay = timeClient.getDay(); - - if (lastDay != -1 && currentDay != lastDay) { - Serial.println("Neuer Tag - Tageszähler wird zurückgesetzt"); - dailyPulses = 0; - saveToEEPROM(); - - // Sofort MQTT Update + if (!mqtt.connected() && millis() - lastAttempt > 30000) { // 30s statt 5s + mqtt.connect("GasMeter_ESP32"); if (mqtt.connected()) { - sendMQTTData(); + mqtt.subscribe("homeassistant/sensor/gaszahler/set"); } + lastAttempt = millis(); } - - lastDay = currentDay; } -// =================== WATCHDOG & ÜBERWACHUNG =================== +// =================== WiFi mit Auto-Reconnect =================== +bool connectWiFi() { + if (WiFi.status() == WL_CONNECTED) return true; + + Serial.println("WiFi reconnect..."); + WiFi.disconnect(true); + delay(100); + WiFi.mode(WIFI_STA); + WiFi.begin(SSID, PASS); + + int attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < 20) { + delay(500); + Serial.print("."); + attempts++; + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("\nWiFi OK"); + return true; + } + return false; +} + +// =================== Speicher-Überwachung =================== +void checkMemory() { + static unsigned long lastHeapCheck = 0; + + if (millis() - lastHeapCheck > 60000) { + size_t freeHeap = ESP.getFreeHeap(); + size_t minHeap = ESP.getMinFreeHeap(); + size_t maxAlloc = ESP.getMaxAllocHeap(); + + Serial.printf("Heap: %d (min %d, max alloc %d)\n", freeHeap, minHeap, maxAlloc); + + // Bei Speichermangel neustarten + if (freeHeap < 30000) { + Serial.println("CRITICAL: Low memory!"); + ESP.restart(); + } + + lastHeapCheck = millis(); + } +} + +// =================== Watchdog =================== void setupWatchdog() { - // Task Watchdog für Hauptschleife (5 Sekunden) - esp_task_wdt_init(5, true); + esp_task_wdt_init(10, true); // 10 Sekunden esp_task_wdt_add(NULL); } +void feedWatchdog() { + esp_task_wdt_reset(); +} + // =================== SETUP =================== void setup() { Serial.begin(115200); delay(1000); - Serial.println("\n================================="); - Serial.println(" ESP32 Gaszähler v2.0"); - Serial.println("================================="); - Serial.printf("Chip-ID: %08X\n", (uint32_t)ESP.getEfuseMac()); - Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap()); - - // EEPROM Daten laden - loadFromEEPROM(); - - // Reed-Sensor initialisieren - pinMode(REED_PIN, INPUT_PULLUP); - - // Initialen Zustand lesen (INVERTIERT!) - lastReedState = digitalRead(REED_PIN); - Serial.printf("Startzustand Reed-Pin: %s\n", - lastReedState ? "HIGH (Magnet vorhanden)" : "LOW (kein Magnet)"); - - // Interrupt konfigurieren - attachInterrupt(digitalPinToInterrupt(REED_PIN), handleReedInterrupt, CHANGE); - Serial.println("Interrupt aktiviert (CHANGE)"); - - // WiFi verbinden - Serial.print("Verbinde mit WiFi"); - WiFi.begin(SSID, PASS); - WiFi.setSleep(false); // Bessere Stabilität - - unsigned long wifiTimeout = millis() + 30000; - while (WiFi.status() != WL_CONNECTED && millis() < wifiTimeout) { - delay(500); - Serial.print("."); - } - - if (WiFi.status() == WL_CONNECTED) { - Serial.println("\nWiFi verbunden!"); - Serial.printf("IP: %s, RSSI: %d dBm\n", - WiFi.localIP().toString().c_str(), WiFi.RSSI()); - } else { - Serial.println("\nWiFi Fehler - im Offline-Modus"); - } - - // NTP Client - timeClient.begin(); - timeClient.update(); - Serial.printf("NTP Zeit: %s\n", timeClient.getFormattedTime().c_str()); - - // MQTT Client - mqtt.setServer(MQTT_SERVER, 1883); - mqtt.setCallback(mqttCallback); - mqtt.setBufferSize(512); - mqtt.setKeepAlive(30); + Serial.println("\n\n=== ESP32 GAS METER V3.0 ==="); // Watchdog setupWatchdog(); - Serial.println("Setup abgeschlossen!"); - Serial.println("=================================\n"); + // EEPROM + loadFromEEPROM(); + Serial.printf("Geladen: Total=%lu, Daily=%lu\n", totalPulses, dailyPulses); + + // GPIO + pinMode(REED_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(REED_PIN), handleReedInterrupt, CHANGE); + lastReedState = digitalRead(REED_PIN); + + // WiFi + WiFi.setSleep(false); // Kein Deep Sleep + connectWiFi(); + + // NTP + timeClient.begin(); + timeClient.update(); + + // MQTT + mqtt.setServer(MQTT_SERVER, MQTT_PORT); + mqtt.setCallback(mqttCallback); + mqtt.setBufferSize(512); + mqtt.setKeepAlive(60); // Längerer Keep-Alive + + Serial.println("Setup fertig!\n"); } -// =================== HAUPTLOOP =================== +// =================== LOOP =================== void loop() { - static unsigned long lastMQTTSend = 0; - static unsigned long lastSerialOutput = 0; - static unsigned long lastStateCheck = 0; + // 1. WATCHDOG - IMMER als erstes! + feedWatchdog(); - // Watchdog füttern - esp_task_wdt_reset(); + // 2. SPEICHER prüfen + checkMemory(); - // Impulse in Hauptschleife verarbeiten (thread-sicher) + // 3. IMPULSE verarbeiten (kurz, ohne delay) if (pulseFlag) { - // Warte kurze Zeit für Stabilität - delay(1); + totalPulses++; + dailyPulses++; + Serial.printf("Impuls! Total: %lu, Daily: %lu\n", totalPulses, dailyPulses); - // Nochmal prüfen (Entprellung) - if (digitalRead(REED_PIN) == LOW) { - totalPulses++; - dailyPulses++; - - Serial.printf("[IMPULS] Total: %lu (%.3f m³), Heute: %lu (%.3f m³)\n", - totalPulses, totalPulses / PULSES_PER_M3, - dailyPulses, dailyPulses / PULSES_PER_M3); - - // Bei jedem Impuls speichern (für maximale Datenintegrität) + // EEPROM nur alle 10 Impulse oder alle 5 Minuten + static unsigned long lastSave = 0; + if (totalPulses % 10 == 0 || millis() - lastSave > 300000) { saveToEEPROM(); - - // Sofort MQTT Update bei aktivem Impuls - if (mqtt.connected() && (millis() - lastMQTTSend > 1000)) { - sendMQTTData(); - lastMQTTSend = millis(); - } + lastSave = millis(); + } + + // Sofort MQTT senden bei Impuls + if (mqtt.connected()) { + sendMQTTData(); } pulseFlag = false; } - // Netzwerk Updates - if (WiFi.status() == WL_CONNECTED) { - timeClient.update(); + // 4. WiFi mit Timeout + if (millis() - lastWiFiCheck > WIFI_CHECK_INTERVAL) { + if (!connectWiFi()) { + Serial.println("WiFi-Fehler, neustart..."); + ESP.restart(); + } + lastWiFiCheck = millis(); + } + + // 5. NTP Update (nur stündlich) + if (millis() - lastNTP > NTP_INTERVAL) { + if (WiFi.status() == WL_CONNECTED) { + timeClient.update(); + } + lastNTP = millis(); + } + + // 6. MQTT mit Timeout + if (mqtt.connected()) { mqtt.loop(); + + if (millis() - lastMQTT > MQTT_INTERVAL) { + sendMQTTData(); + lastMQTT = millis(); + } + } else { reconnectMQTT(); - checkDailyReset(); } - // Regelmäßiges MQTT Update (alle 30 Sekunden) - if (mqtt.connected() && (millis() - lastMQTTSend > 30000)) { - sendMQTTData(); - lastMQTTSend = millis(); + // 7. Status nur alle 5 Minuten + if (millis() - lastStatus > STATUS_INTERVAL) { + Serial.printf("Status - Uptime: %lu min, WiFi: %d dBm, Heap: %d\n", + millis() / 60000, WiFi.RSSI(), ESP.getFreeHeap()); + lastStatus = millis(); } - // Serieller Status (alle 60 Sekunden) - if (millis() - lastSerialOutput > 60000) { - Serial.printf("[STATUS] Heap: %d, Uptime: %lu s, RSSI: %d dBm\n", - ESP.getFreeHeap(), millis() / 1000, WiFi.RSSI()); - lastSerialOutput = millis(); - } - - // Reed-Pin Status prüfen (Debug, alle 10s) - if (millis() - lastStateCheck > 10000) { - bool currentState = digitalRead(REED_PIN); - Serial.printf("[SENSOR] Pin: %s\n", - currentState ? "HIGH (Magnet da)" : "LOW (Magnet weg)"); - lastStateCheck = millis(); - } - - delay(10); // Kleine Pause für Stabilität + // 8. KEINE langen delays! Max 50ms + delay(10); } \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html