This commit is contained in:
jens 2026-02-28 17:50:30 +01:00
parent bd09b9d570
commit 08d1afb266
5 changed files with 335 additions and 322 deletions

37
include/README Normal file
View File

@ -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

46
lib/README Normal file
View File

@ -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 <Foo.h>
#include <Bar.h>
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

View File

@ -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

View File

@ -4,181 +4,125 @@
#include <WiFiUdp.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <esp_task_wdt.h>
#include <esp_task_wdt.h> // Watchdog
#include <esp_wifi.h> // WiFi-Management
#include <esp_heap_caps.h> // 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"] = "";
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);
}

11
test/README Normal file
View File

@ -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