Release 2.4.0 (build 230)
Firmware binaries:
- firmware_zigbee.bin – ESP32-C5 ZigBee + BLE variant
- firmware_matter.bin – ESP32-C5 Matter + BLE variant
- firmware_esp8266.bin – ESP8266 (OS 3.x)
All releases: https://github.com/opensprinklershop/OpenSprinkler-Firmware/releases
Added
- Gardena smart system: Einrichtung nur mit Application Key und Secret, gemeinsamer Cache, Ventile auch auf OSPi: Der Gardena-Client (
sensor_gardena.cpp) authentifiziert sich jetzt per OAuthclient_credentialsmit Application Key und Application Secret aus dem Husqvarna Developer Portal, genau wie ioBroker/Home Assistant; der Browser-Login-Assistent mit Refresh-Token bleibt als Alternative (der von Husqvarna rotierte Refresh-Token wird jetzt zurückgeschrieben). Das Access-Token liegt nur noch im RAM und wird 5 Minuten vor Ablauf erneuert; bisher wurde es in den 320-Byte-Optionsslot geschrieben, dabei abgeschnitten und die Konfiguration damit ungültig („Zugangsdaten weg“, Ticket 4QU-39X-1SNH). Alle Gardena-Sensoren und -Stationen teilen sich eine zwischengespeicherte Geräteliste: höchstens ein Standort-Abruf propollSekunden (Standard 30 min, min. 5 min, Optionpoll), die Standort-ID wird einmal ermittelt und gespeichert, Fehler lösen exponentiellen Backoff aus (5 min bis 1 h, bei HTTP 429 1 h), die Sensoren liefern währenddessen die letzten gültigen Werte (bis 6 h). Damit bleibt eine Installation mit mehreren Sensoren weit unter dem Husqvarna-Wochenlimit (700–1000 Anfragen pro Application, Sperre 24 h). Der Stationstyp „Gardena“ (Water Control/Irrigation Control) schaltet jetzt auch auf dem OSPi; die Laufzeit wird auf volle Minuten gerundet./galiefert das Secret nicht mehr zurück (nurhas_secret),/co?gardena=mischt die Felder in die gespeicherte Konfiguration (fehlende Felder bleiben erhalten,reset:1löscht),/glliefert Diagnose (token,requests,http,cache_age,poll,fails,error;refresh=1erzwingt einen Abruf,locations=1listet die Standorte) (Ticket P7H-B89-Q79Q). - Wassermenge des laufenden Zyklus in der Statuszeile:
/jcliefert bei Durchflusssensor zusätzlichflcrun, die Impulse seit Beginn des aktuellen Bewässerungszyklus (genau der Wert, den der nächste Durchfluss-Logeintrag schreibt; 0 im Leerlauf). Die App zeigt daraus neben dem Durchfluss „Volumen: x L“ an; bei kurzen Läufen innerhalb eines langen Zyklus war bisher nur die Momentanrate (meist 0 L/min) sichtbar (Ticket DE1-PA5-PDDV).
Fixed
- Wasserzähler-Sensor (Impulse, Typ 97) verlor nach einem Stromausfall die Impulse seit der letzten Ablesung: Der Impulszähler lag nur im RAM und wurde bisher nur vor einem Software-Neustart gesichert. Ein Stromausfall oder eine nächtliche Zeitschaltuhr zwischen Ablesung und Morgen löschte alles, was seit der Ablesung gezählt war; bei einer 24-h-Ablesung um 01:06 und Abschaltung um 04:45 fehlte täglich der Nachtlauf, der Tageswert lag unter dem Durchfluss-Log, an Tagen ohne Nachtlauf stimmte er exakt. Der Zählerstand wird jetzt zusätzlich am Ende jedes Bewässerungszyklus (beim Schreiben des Durchfluss-Logeintrags) und nach jeder Ablesung des Sensors in
flowcnt.datgesichert und beim Start wieder geladen; nach einem Stromausfall fehlen nur noch Impulse nach dem letzten Sicherungspunkt. Der Sensor behält dabei seine gespeicherte Basis (nativedata) statt bei 0 anzufangen (Ticket 8V1-8LT-M81W). - Verbrauchssensoren: gleiche Messwerte wurden nicht protokolliert: Der Sensorlog schreibt einen Eintrag nur bei geändertem Wert (Schwelle 0,01) oder nach mehr als 24 h. Für Verbrauchseinheiten (L/gal pro Intervall) ist das falsch: Zwei gleiche Tageswerte sind zweimal diese Menge; der zweite fehlte im Log und damit in den Wochen-/Monatssummen (
calc_sensorlogs). Verbrauchswerte größer 0 werden jetzt immer protokolliert, Pegelsensoren wie bisher nur bei Änderung (Ticket M9W-TM2-3S17). - NTP-Abgleich ohne Antwort alle 5 s wiederholt: Scheiterte der Abgleich (z. B. UDP 123 durch Router/Firewall blockiert), versuchte
perform_ntp_sync()es ohne Ende alle 5 s mit bis zu drei Servern, rund 50.000 Anfragen am Tag an den öffentlichen Pool und jedes Mal bis zu 6 s blockierte Hauptschleife. Der Abstand verdoppelt sich jetzt nach jedem Fehlschlag von 5 s bis höchstens 10 min und springt nach einem erfolgreichen Abgleich zurück (Ticket PVS-7BB-J63B). - Absturz beim Speichern eines kopierten Sensors (ESP8266):
/sr(Sensor sofort lesen) führte die Abfrage synchron im HTTP-Handler aus; bei HTTP-/Cloud-Sensoren lief der komplette Client auf dem OTF-/WebSocket-Stack und riss den ESP8266 in einen Watchdog-Neustart, der in der Diagnose als „Einschalten“ erschien, alle Sensoren blieben danach bis zur nächsten Ablesung ungültig./srterminiert die Ablesung jetzt nur noch für den nächsten Durchlauf der Hauptschleife und antwortet mitstatus -5(ausstehend) (Tickets 8V1-8LT-M81W, JE4-T69-22RQ). - Neustartgrund „Einschalten“ bei Abstürzen: Die Firmware setzt den gespeicherten Grund beim Start vorsorglich auf „Einschalten“; nach einer Exception oder einem Watchdog stand deshalb „Einschalten“ in der Diagnose. Fehlt ein aufgezeichneter Grund, wird jetzt der Reset-Grund des SoC ausgewertet: neue Codes 13 Absturz (Exception/Panic), 14 Watchdog, 15 Brownout (ESP32), 16 Software-Neustart ohne Grund; die App zeigt sie an (Ticket 8V1-8LT-M81W).
file_read_block()hinter dem Dateiende: Ein fehlgeschlagenesseek()ließ die Position bei 0, der Aufruf lieferte stillschweigend den ersten Block der Datei. Bei einersopts.dataus einem älteren Build mit weniger Slots erschien so der Passwort-Hash (Slot 0) als Slot 15 in/ub, auch mit genulltem Puffer. Lesen hinter dem Dateiende liefert jetzt 0 Byte (Ticket YXH-BU6-G5P6).- Gardena-Sensoren lieferten nie Werte: Beim Übernehmen der Gardena-Antwort stand
attrs["soilHumidity"]["value"] | nullptr; in ArduinoJson 7 ergibt| nullptrimmer null, die Feuchte- und Temperaturwerte kamen also nie bei den Sensoren an (lila markiert, 0 %). Die Werte werden jetzt direkt übernommen (Ticket P7H-B89-Q79Q). - Durchfluss 0 L/min bei kurzen Stationsläufen: Die Stationsmessung ignoriert die ersten 90 s eines Laufs (Rohrfüllung, Verhalten aus der Original-Firmware) und braucht danach zwei Impulse; Läufe unter etwa zwei Minuten, wie sie bei wetterbedingt verkürzten Programmen üblich sind, ergaben immer 0. Liefert das Fenster weniger als zwei Impulse, wird die Rate jetzt aus allen Impulsen des Laufs gebildet (leicht erhöht durch die Füllphase, statt 0) (Ticket 6RM-3W9-ATH4).
- W5500-SPI auf dem ESP8266 mit 20 statt 40 MHz: Der Adapter sitzt auf einem Steckverbinder ohne Terminierung; 40 MHz lagen für die Leitungsführung an der Grenze (Ticket JE4-T69-22RQ).
- Programmverweise in Monitoren und Programmanpassungen beim Verschieben/Löschen: Monitore (
prog) und Sensor-Programmanpassungen speichern die Programmnummer. Beim Verschieben nach oben (/up), Löschen (/dp, auch Run-once-Programme nach dem Lauf) und „alle löschen“ wurden die Nummern bisher nicht mitgezogen, der Monitor startete oder stoppte danach ein anderes Programm (replace_pid()existierte, wurde aber nie aufgerufen).ProgramData::del()/moveup()/eraseall()rufen jetztsensors_program_deleted()/_swapped()/_erased(); ein gelöschtes Programm wird im Monitor auf „keins“ gesetzt (Ticket DE1-PA5-PDDV). - Sensorwerte nach Neustart „ungültig“:
sensor_load()setzte beim Start alle Sensoren auf ungültig; ein Sensor mit 24-h-Leseintervall (Tageswasserzähler) blieb damit nach jedem Neustart bis zur nächsten Ablesung lila markiert, Monitore sahen keinen Wert. Sensoren mit gespeicherter Ablesung starten jetzt gültig, die Fehlertoleranz entscheidet weiterhin über den Verfall (Ticket 8V1-8LT-M81W). - Startzeitpunkt nach Stromausfall:
powerup_lasttimewurde vor dem ersten NTP-Abgleich aus der RTC übernommen; auf OS 3.x bleibt die RTC bei einem Stromausfall stehen, die Diagnose zeigte daher die Ausschalt- statt der Einschaltzeit und die Boot-Verzögerungen liefen zu früh ab. Der erste NTP-Abgleich verschiebt den Startzeitpunkt um die Korrektur (Ticket 8V1-8LT-M81W). - Wasserzähler (Impuls) nach Stromausfall: Der Neustart wird jetzt am Zeitstempel erkannt (
powerup_lasttimenach der letzten Ablesung) statt nur am kleineren Zählerstand; ein Tag mit mehr Wasser als der Vortag lieferte sonst nach einem täglichen Stromzyklus einen zu kleinen Wert. Nach jeder erfolgreichen Ablesung wird die Sensorkonfiguration gespeichert, damit das Abfrageraster (last) und die Zählerbasis einen Stromausfall vor der nächsten stündlichen Sicherung überleben (Ticket 8V1-8LT-M81W). /dlmit überlangemday-Parameter setzte das Gerät zurück:make_logfile_name()parkte den Namen in den letzten 10 Bytes vontmp_buffer; ab 310 Zeichen lief die Kopie über das Pufferende. Der Handler akzeptiert nur nochalloder bis zu 8 Ziffern, der Helfer kürzt auf 9 Zeichen (Ticket YXH-BU6-G5P6, Finding 8).- W5500-Ethernet-Hänger auf OpenSprinkler 3.x (ESP8266): Der W5500 des Ethernet-Adapters blieb sporadisch in einem Zustand hängen, in dem die Link-LED leuchtet, aber keine Frames mehr gesendet oder empfangen werden (Router sieht keine ARP-Antworten, Gerät zeigt weiter seine IP). Weil die RESET-Leitung des Moduls nicht an einen GPIO geführt ist, half auch ein Neustart nicht:
eth.begin()löst nur den MR-Software-Reset aus, der die PHY unberührt lässt, DHCP endete danach in 169.254.x.x. Neu (eth_w5500.cpp): Die PHY wird per SPI überPHYCFGRzurückgesetzt (Power-down-Zyklus, damit der Switch den Link fallen sieht, dann Auto-Negotiation), Socket 0 wird im MACRAW-Modus neu geöffnet und ein Gratuitous ARP gesendet. Das passiert einmal beim Start der Ethernet-Verbindung und im Netzwerk-Check, sobald der Gateway-Ping fehlschlägt oder der Link verloren geht; der Neustart nach drei Fehlschlägen bleibt als letzte Stufe. Der Netzwerk-Check läuft auf Ethernet jetzt auch während eines laufenden Programms (ein dauerhaft geplanter Kreis hatte ihn bisher komplett unterdrückt), der Neustart wartet weiterhin auf Leerlauf. Auf dem ESP32-C5 (ESP-IDF-Treiber mit eigener Task und Timeouts, PHY-Reset beim Treiberstart) gibt es dasselbe Verhalten ohne Neustart überesp_eth_stop()/esp_eth_start(), das die PHY per Autonegotiation-Restart zurücksetzt und DHCP neu anstößt./jcliefert die Anzahl der Wiederherstellungen seit dem Start alsethrst;/cv?ethrst=1stößt die Wiederherstellung manuell an (ESP8266; die Antwort geht vor dem Reset raus,item= Link-Status davor). Dazu ersetzt eine gepatchte Kopie des Core-Treibers (lib/lwIP_w5500) die Version aus dem ESP8266-Core:setSn_CR()undsendFrame()warteten unbegrenzt auf das Kommandoregister bzw. SENDOK, was bei fehlendem PHY-Link (Kabel gezogen, PHY-Hänger) jede lwIP-Sendung in einen Soft-WDT-Reset laufen ließ, direkt nachbegin()sogar in eine Boot-Schleife. Jetzt verwirftsendFrame()Frames ohne Link sofort, alle Warteschleifen sind auf 20 bis 50 ms begrenzt. Auf dem Benchgerät (OS 3.2, W5500) überlebt die Firmware 5 bzw. 10 s PHY-Power-down bei laufendem System ohne Neustart (Debug-Trigger/cv?ethpd=<ms>) (Tickets JE4-T69-22RQ, 6RM-3W9-ATH4).
Security
Die Punkte in diesem Abschnitt gehen auf den Bericht von Zach (github.com/zstinnett, Ticket YXH-BU6-G5P6) zurück, danke dafür.
Hinweis zum Update von 229 auf 230: Das Online-Update von Build 229 lädt noch ungeprüft über HTTP; dieses eine Update bitte aus einem vertrauenswürdigen Netz anstoßen. Ab 230 ist jedes weitere ESP8266-Update signiert.
- ESP8266 Online-Update: signierte Firmware: Das Online-Update auf dem ESP8266 lädt Manifest und Binary über Klartext-HTTP (BearSSL kann die Serverkette auf dem RAM-knappen ESP8266 nicht prüfen); bisher wurde dabei nur die vom selben Server gelieferte
x-MD5-Prüfsumme verglichen, die SHA-256 aus dem Manifest wurde gar nicht ausgewertet. Release-Binaries für den ESP8266 tragen jetzt eine RSA-2048/SHA-256-Signatur im Format des ESP8266-Arduino-Cores (fw.shsigniert beim Kopieren ins Upgrade-Verzeichnis, SchlüsselESP8266_SIGNING_KEYin.env). Der Updater installiert den öffentlichen Schlüssel (online_update_pubkey.h) alsBearSSL::SigningVerifier, prüft zusätzlich die SHA-256 aus dem Manifest über den gesamten Download und verwirft Images, die nicht beide Prüfungen bestehen. Manuelle Uploads über/updateund serielles Flashen bleiben unverändert möglich.fw.shverweigert die Veröffentlichung unsignierter ESP8266-Binaries (Ticket YXH-BU6-G5P6). - Login-Sperre auf allen Plattformen: Die Brute-Force-Sperre (5 Fehlversuche in 5 Minuten sperren die API für 30 s) gilt jetzt auch auf ESP8266 und OSPi, nicht nur in EN18031-Builds, und schützt neben den HTTP-Endpunkten auch
/updateund die MCP-Authentifizierung (pw,X-OS-Password, Bearer). Die Option „Passwort ignorieren“ bleibt außerhalb von EN18031-Builds wählbar (Ticket YXH-BU6-G5P6). - CORS nur noch für bekannte Oberflächen (Finding 3):
Access-Control-Allow-Origin: *auf jeder Antwort erlaubte jeder im Browser des Besitzers geöffneten Webseite, API-Antworten zu lesen. Der Header wird jetzt nur noch für*.opensprinklershop.de,*.opensprinkler.com,localhost/127.0.0.1und den Host der konfigurierten UI-Quelle (jsp) gesetzt (Vary: Origin); die vom Gerät selbst ausgelieferte Seite braucht keinen CORS-Header. Der Upload-Server (/update, Port 8080) behält den Wildcard, da er nur mit Passwort schreibt und nichts Lesbares liefert. - Login-Sperre pro Quelladresse: Die Sperre nach fünf Fehlversuchen gilt jetzt je Client-IP (Tabelle mit 8 Einträgen; Cloud-Relay, MCP und
/updateteilen sich den Eintrag „unbekannt“), damit ein Gerät im LAN den Besitzer nicht dauerhaft aussperren kann. Dazu reicht das OpenThings-Framework die Peer-Adresse (Request::getRemoteIP(), auf OSPi pergetpeername) an die Handler durch. /dbnur noch mit Passwort (Finding 9): Build-Datum, Heap, IP- und WLAN-Details werden ohne gültigespwnicht mehr ausgegeben;fw.sh, die App und der externe MCP-Server senden das Passwort bereits.- UI-/Wetter-URL geprüft (Finding 6):
/cuakzeptiert fürjspundwspnur nochhttp(s)://-URLs ohne Anführungszeichen, spitze Klammern, Leer- und Steuerzeichen (sonstresult 17), da der Wert unmaskiert in das<script src>der Geräteseite eingesetzt wird. - Geheimnisse, zweite Runde (Finding 5):
/jc//jamaskieren jetzt auch den Wetter-API-Schlüssel (wto.key);/ublässt den IFTTT-Schlüssel weg und maskiert Wetter-Schlüssel, MQTT-/E-Mail-Passwort, FYTA-Token und Gardena-Secret/Refresh-Token je Slot, der OTC-Token bleibt (er ist ohne das Gerätepasswort wertlos und wird für die Wiederherstellung gebraucht)./ubnullt den Lesepuffer vor jedem Slot: Bei einer älteren, kürzerensopts.datlieferte ein Slot hinter dem Dateiende den vorherigen Heap-Inhalt, bei einem Tester war das der Passwort-Hash in Slot 15. - Upload-Server (
/update, Port 8080) mit derselben CORS-Allowlist wie die API (Originwird percollectHeaderserfasst). - Optionale SHA-256-Prüfung beim manuellen Upload:
/updatenimmt ein Formularfeldsha256(64 Hex-Zeichen) entgegen, berechnet den Digest über den Stream und verwirft das Image vor der Aktivierung bei Abweichung (result 64,item sha256). Ohne Feld bleibt der Upload wie bisher. - Keine gespeicherten Geheimnisse mehr in der API (Finding 5):
/jcliefert MQTT- und E-Mail-Passwort sowie InfluxDB-Token (/ig) als********und den IFTTT-Schlüssel nur noch als gesetzt/leer; schickt die App die Maske zurück, bleibt der gespeicherte Wert erhalten, ein neuer Wert ersetzt ihn./ubenthält weder den Passwort-Hash noch das WLAN-Passwort mehr (beides ist über die API ohnehin nicht wiederherstellbar). Der OTC-Token bleibt, er ist der Fernzugriffsschlüssel des Besitzers. - Stationsnamen in der App escaped (Finding 7): Dashboard, Statuszeile und Master-Auswahl zeigen Stationsnamen HTML-escaped an.
- OpenThings-Framework-Schlüssel entfernt:
server_key.derund das Arrayopensprinkler_key_DERincert.h(der bis Build 185 gemeinsame TLS-Schlüssel) sind aus der Bibliothek entfernt;/ca.derliefert weiterhin nur das Zertifikat. - Build kompilierte eine veraltete Kopie des OpenThings-Frameworks:
lib_deps = file://…ließ PlatformIO die Bibliothek nach.pio/libdepskopieren und die Kopie (Stand März) weiterverwenden; Änderungen im Checkout kamen nie in die Binaries, darunter das Content-Length-Limit der Beta vom 13.10. Jetztsymlink://…, die Kopien wurden gelöscht. - Anfragegröße begrenzt (OpenThings-Framework): Der HTTP-Server reservierte für jeden Request-Body einen Heap-Puffer in Höhe der angegebenen
Content-Lengthohne Obergrenze; eine unauthentifizierte Anfrage mit ~24 KB Content-Length ließ den ESP8266 mangels Heap neu starten. Bodies überOTF_MAX_BODY_LENGTH(ESP8266 8 KB, sonst 64 KB) werden mit 413 abgewiesen, ein fehlgeschlagenesnewmit 503 statt Absturz (Ticket YXH-BU6-G5P6, Finding 10). - Repository-Bereinigung:
cookies.txt(HESK-Sitzungscookie), die RainMaker-Zertifikatspartitionesp_secure_cert_data/mit privatem EC-Schlüssel und drei Build-/Test-Logs (build_libs.log,test.log,upload.log) aus dem Repository entfernt (git rm --cached, die Dateien blieben nach dem ersten Commit noch getrackt) und per.gitignoregesperrt; das Gerätezertifikat sollte in RainMaker widerrufen werden. Der bis Build 185 eingebaute, gemeinsame HTTPS-Serverschlüssel wurde aus der OpenThings-Framework-Library und dem App-Repository entfernt; die App führt das zugehörige Zertifikat nicht mehr als Trust-Anchor. NeueSECURITY.mdmit Meldeweg für Sicherheitslücken (Ticket YXH-BU6-G5P6).