Fensterpiepser ESP32 — Architekturübersicht

NodeMCU-32S  ·  Arduino/ESP-IDF  ·  v2.0.8  ·  Varianten: EG / DG

1 · Tasks & Interrupts – Gesamtübersicht

ESP32 NodeMCU-32S · 240 MHz · Xtensa LX6 Dual-Core Core 0 beepTask Prio 5 · Core 0 · Stack 2 KB · Wartet auf beepSemaphore (gegeben alle 10 ms von timerIsr) · Short-Beep-Timing (30 ms Puls) · PiepPattern::TaskCyclic() → PiepMode → toneRequest → GPIO18 rfDecodeTask Prio 4 · Core 0 · Stack 3 KB · Wartet auf rfEventQueue (RfEvent_t: dtMicros, pinValue) · receiverSw / Aiggend / Oval / Kerui / Thermo .Isr() · Setzt lastReceivedKeyNum / bNewData für loop WiFi / TCP-IP Stack ESP-IDF intern · Autonome Tasks (pro_cpu_0) · PubSubClient nutzt WiFiClient · SSID: Elektrosmog · Broker: flokke.de:1883 NTP: ptbtime3.ptb.de (GMT+1 / DST+1) Hostname: FENSTER_PIEPSER_NODEMCU_32_S_EG OTA: WebServer Port 80 (/update) Reset nach 24 h (wenn alle Fenster zu) Core 1 timerIsr hw_timer 0 · 1 MHz · Alarm 10 000 µs · ++count; if count==10 → tDecis++ (volatile) · xSemaphoreGiveFromISR(beepSemaphore) + portYIELD_FROM_ISR ← gesamte Beep-Logik jetzt in beepTask pinLevelChangeIsr GPIO 26 · CHANGE · INPUT_PULLUP · dtMicros = micros() − lastMics · pinVal = digitalRead(26) · Filter: loopCountSincePause < 2 || dtMicros < 100 µs → skip · xQueueSendFromISR(rfEventQueue, &ev) + portYIELD_FROM_ISR loopTask (Arduino loop) Prio 1 · Core 1 · vTaskDelay 5 ms 1 handleKeyReceived() – Empfangene Keys → Fenster-State → MQTT 2 handleDispUpdate() – SPI-Display (1 s / 10 s Interval) 3 handleWifiConnection() – Reconnect nach 30 s, Restart on Timeout 4 myMqttClient.onLoop() – MQTT keepalive, RSSI alle 600 s 5 UpdateHandler() – HTTP OTA (WebServer Port 80) 6 updatePiepPattern() – Pause-Modus nach 6 min Dauerbeeep 7 xcpLoop() – XCP-Events 10 / 100 / 500 / 1000 ms 8 loopCountSincePause++ – RF-Debounce Zähler 9 vTaskDelay(5 ms) – gibt Core 1 für ISRs frei beepSemaphore rfEventQueue[32] keyNum / dir

2 · FreeRTOS Synchronisationsobjekte

ObjektTypGrößeProduzent (ISR)Konsument (Task)Zweck
beepSemaphore Binary Semaphore timerIsr alle 10 ms beepTask Core 0, Prio 5 Takt für Beep-State-Machine; ersetzt direkten ISR-Aufruf von PiepPattern::TaskCyclic()
rfEventQueue Queue – RfEvent_t32 Einträge × 5 B pinLevelChangeIsr bei jeder Flanke rfDecodeTask Core 0, Prio 4 Überträgt {dtMicros, pinValue}; alle 5 Receiver-State-Machines laufen jetzt im Task-Kontext

3 · Signalflüsse

RF-Empfangspfad (433 MHz OOK) 433 MHz Empfänger pinLevelChange Isr (Core 1) rfEventQueue 32 × RfEvent_t rfDecodeTask Core 0 Prio 4 ReceiverSmartWares ReceiverAiggend ReceiverOval ReceiverKerui ReceiverFunkThermo loopTask handleKey MQTT publish flokke.de:1883 Beep-Pfad (Buzzer-Ansteuerung) timerIsr 10 ms · Core 1 beepSemaphore binary beepTask Core 0 Prio 5 PiepPattern TaskCyclic() piepFast ×2 piepSlow ×1 piepPause ×120 toneRequest bitmask GPIO 18 PIEP_VCC 🔔 GPIO33 PIEP_PWM 2 kHz · LEDC Ch12 duty 127/255 (fix) bRequestShortBeep (volatile) gesetzt von loopTask → 30 ms Bestätigungs-Piep

4 · 433-MHz-Receiver

ObjektProtokollKey-BytesAnzahl KeysKey-Index-OffsetBasisklasse
receiverSwSmartWares Manchester OOK8 B220Receiver433
receiverAiggend3-Byte-Key PWM3 B + 1 Richtungs-Bkonfigurierbar+30Receiver3ByteKey
receiverOval3-Byte-Key PWM3 B + 1 Richtungs-Bkonfigurierbar+40Receiver3ByteKey
receiverKerui3-Byte-Key PWM3 B + 1 Richtungs-Bkonfigurierbar+50Receiver3ByteKey
receiverThermoFunk-Thermometer PWM4 B (36 bit, 4 bit ignoriert)Receiver433
Alle .Isr()-Aufrufe erfolgen in rfDecodeTask (Core 0) via rfEventQueue. Debounce: loopCountSincePause < 2 → ISR verwirft Flanke nach Key-Empfang.

5 · Fenster-Objekte

Variante EG (VERSION_DG_ONLY=0, alleFenster[4])
NameSensor-TypKey-Nr (mit Offset)Wait-Zeit-Quelle
fensterWzKerui2 + 50 = 52waitTimeDs (Temperatur)
fensterBadUntenAiggend3 + 30 = 33waitTimeDs
fensterKuecheOval2 + 40 = 42waitTimeDs
fensterToniNeuOval1 + 40 = 41waitTimeDs
Variante DG (VERSION_DG_ONLY=1, alleFenster[4])
NameSensor-TypKey-Nr (mit Offset)Wait-Zeit-Quelle
fensterBadObenAiggend1 + 30 = 31waitTimeDs
fensterFloAiggend2 + 30 = 32waitTimeDs
fensterHannaOval5 + 40 = 45waitTimeDs
fensterFloBueroOval4 + 40 = 44waitTimeDs
Sonder-Objekte (immer instanziiert)
NameSensor-TypKey-NrBesonderheit
fensterGefrierSmartWares10Feste Wait-Zeit: 30 s (hardcodiert in ShallPiep())

6 · Beep-Muster (PiepPattern)

ModusonTimeoffTimerequestCountReihenfolge
piepFast20 ms80 ms2 Mal1.
piepSlow500 ms100 ms1 Mal2.
piepPauseLong0 ms1000 ms120 × = 60 s3. (Standardpause)
piepPauseShort0 ms1000 ms20 × = 10 s3. (nach 6 min Dauerbeeep)
Pattern läuft zyklisch: fast → slow → pause(Long/Short) → fast → …   Start/Stop über piepPattern→startRequest / stopRequest (volatile).   Stopp bei Nacht (bIsNightTime) oder alle Fenster zu.

7 · Wartezeit-Berechnung (waitTimeDs)

AußentemperaturWartezeitErklärung
< −5 °C5 minKurz – kalte Luft, schnelles Lüften
−5 … 19,5 °C5 … 30 min (linear)Lineare Interpolation: 5 + (T+5)×25/24,5
19,5 … 25 °C~2 WochenKein Alarm – Sommer, Lüften sinnvoll
> 25 °C30 minHeiß – kurze Alarmverzögerung
kein Sensor-Wert10 minDefault bis erste Thermo-Meldung
Quelle: receiverThermo (Funk-Thermometer, Aussensensor) · Update bei jedem neuen Thermo-Paket (~50 s Intervall).

8 · GPIO-Belegung

GPIORichtungFunktion
26INPUT_PULLUPDIN_FUNK – 433-MHz-Datensignal → pinLevelChangeIsr
12OUTPUT HIGHDOUT_FUNK_VCC – Versorgung Empfängermodul
32OUTPUT LOWDOUT_FUNK_GND – GND Empfängermodul
18OUTPUTDOUT_PIEP_VCC – Buzzer-Versorgung (geschaltet via toneRequest)
33LEDC PWMDOUT_PIEP_PWM – Buzzer-PWM 2 kHz, Ch 12, duty 127
16OUTPUT HIGHDOUT_TEST_TIMER – Debug-Pin
GPIOFunktionInterface
14TFT_PIN_CLK (SCK)SPI – Adafruit ST7735
(VERSION_BIG_DISPLAY)
13TFT_PIN_MOSI (SDA)
22TFT_PIN_RST (RES)
21TFT_PIN_DC (RS)
15TFT_PIN_CS

9 · MQTT-Topics (Broker: flokke.de:1883)

TopicRetainWannPayload (Beispiel)
fenster/system/EGneinConnect, Reset{"e":"hello","client":"FensterpiepserEG_v2.0.8"}
fenster/status/EGjaFenster bewegt / Beep gestartet{"t":1700000000,"e":"move","n":4,"f":["Wohnz",…],"o":[0,1,…],"b":[0,0,…]}
fenster/temprh/EGjaNeues Thermo-Paket (~50 s){"T":7,"rh":65,"t":1700000000,"raw":3422534}
fenster/rssi/EGneinAlle 600 s{"rssi":-62}
LWT (Will) auf fenster/temprh/EG: {"T":0,"rh":0,"t":0} · User: lightcontrol · Keepalive: 600 s

10 · Geteilte Variablen & volatile-Annotierungen

VariableTypvolatileSchreiberLeserAnmerkung
tDecisuint32_ttimerIsr (Core 1)loopTask, beepTask, Fenster::ShallPiepMonoton steigend, 32-bit-Reads atomar auf LX6
bRequestShortBeepboolloopTask (Core 1)beepTask (Core 0)Cross-Core; single-byte write atomar
loopCountSincePauseuint32_tloopTask (Core 1)pinLevelChangeIsr (Core 1)RF-Debounce nach Key-Empfang
PiepPattern::startRequest
PiepPattern::stopRequest
uint8_tloopTask via Start()/Stop()beepTask via TaskCyclic()Cross-Core; Flags, nicht Zähler
toneRequestuint8_tbeepTask (exklusiv)beepTaskKein Cross-Core-Zugriff mehr nach Refactoring

11 · XCP-Protokoll

EventIntervallAufruf
XcpEvent(0)10 msxcpLoop() in loopTask (millis-basiert)
XcpEvent(1)100 msxcpLoop()
XcpEvent(2)500 msxcpLoop()
XcpEvent(3)1000 msxcpLoop()
XcpPoll() + XcpSendLoop() werden ebenfalls in xcpLoop() aufgerufen. Timing ist millis()-basiert → toleriert den 5-ms-vTaskDelay des loopTask.