Bindet vernetzte Hausgeräte dreier Ökosysteme an Loxone an und bringt sie auf ein gemeinsames Modell. Für den Miniserver sieht ein Miele-Geschirrspüler danach genauso aus wie eine Bosch-Waschmaschine.
| Anbieter | Marken | Anmeldung |
|---|---|---|
| Home Connect | Bosch, Siemens, Neff, Gaggenau, Constructa | OAuth2 Device Flow |
| Miele | Miele@home (3rd Party API) | OAuth2 Authorization Code, Code von Hand |
| SmartThings | Samsung | Personal Access Token — siehe Vorbehalt |
Fassung 0.9.41 — ungeprüft. Das Plugin wurde ohne Entwicklerkonten und ohne Geräte gebaut. Endpunkte und Datenformen stammen aus den Entwicklerdokumentationen, nicht aus einer Messung. Geprüft ist alles übrige: Oberfläche, Endpunkt, Absicherung, Warteschlange, Sprachdateien und die Zuordnung selbst — letztere gegen nachgebaute Antworten in der dokumentierten Form. Schreibende Befehle sind ab Werk gesperrt.
Baustein-Liste in der Schreibweise des Leitungswerkzeugs, gemeinsame Sprachausgabe 1.1.2.
- Baustein-Liste (Reiter Einbindung in Loxone): Am Statusbaustein #33 heißen die Eingänge jetzt
V1,V2,V3wie in Loxone Config. Alle anderen Zeilen waren schon in dieser Form. Gleiche Bausteine, gleiche Verbindungen. - Sprachausgabe: Werksart vom Modul. Ab Werk spricht das Plugin über den Loxone Music Server. Das sagt jetzt das Modul selbst: „Loxone Music Server (ab Werk)“ in der Auswahl und der Hinweis, dass erst gesprochen wird, wenn die Angaben eingetragen sind. Die beiden eigenen Sätze sind gestrichen.
- Webport über das Modul: Den Port des LoxBerry-Webservers fragt das Plugin jetzt über die
gemeinsame Sprachausgabe ab. Zuerst kommt die Auskunft von LoxBerry selbst, dann die
general.jsonin denselben drei Schreibweisen wie bisher. Ein Leerzeichen um die Zahl schadet nicht mehr. - Sprachausgabe 1.1.2: gemeinsames Modul und Abschnitt [ANSAGE] mit 157 Sätzen. Dazu aus dem Modul: Zeichenzahl bei kaputtem UTF-8 in Zeichen, die Meldung „Port abgewiesen“ nennt das Feld nicht mehr doppelt, ein Satz statt der rohen Kennung bei einem unbekannten Eintrag im Block der Sprachausgabe.
- Gerendert unter PHP 7.4, 8.4 und 8.5, nicht am Gerät angesehen.
Kopf wie alle Hausplugins: Statusübersicht über den Reitern, Zusammenfassung oben im ersten Reiter.
- Zusammenfassung des Plugins in einem grünen Kasten oben im Reiter Einstellungen.
- Die Statuskacheln über den Reitern bleiben, wie sie sind.
- Nur Oberfläche; gerendert unter PHP 7.4, 8.4 und 8.5, nicht am Gerät angesehen.
Sprachausgabe in Hausform (Entscheidung 40, Stufe 2). Gemessen unter PHP 7.4 und 8.5 gegen Attrappen
(Music Server, MusicServer4Home, eigene Vorlage, Alexa-NG, Chromecast 4 Lox NG), mit dem echten Minutenlauf
bin/ansage.php; nicht am Gerät und nicht an einem echten Lautsprecher.
- Einstellungen der Sprachausgabe kommen jetzt aus dem gemeinsamen Baustein der Plugins dieses Hauses. Neu wählbar ist die Ausgabeart „aus“; ab Werk bleibt es beim Loxone Music Server, und die Ansage selbst ist ab Werk aus. Die Haken „Ansage einschalten“, „Störung“, „Fernstart-Freigabe erloschen“ und die Ruhezeit bleiben, wie sie sind.
- Adresse und Vorlage müssen im Heimnetz liegen (private IPv4-Bereiche, Namen ohne Punkt oder mit
.local,.lan,.home,.fritz.box,.intern…). Eine Adresse im Internet wird beim Speichern beanstandet, beim Zurückspielen abgewiesen und vor jedem Senden noch einmal geprüft. - Zonen als Zahlen mit Komma (je wahlweise
~Lautstärke1 bis 100), Lautstärken 1 bis 100 oder leer, Vorlage höchstens 500 Zeichen. Bei einer Beanstandung wird nichts gespeichert, die Eingaben bleiben im Formular stehen. - Die Testansage im Reiter Test meldet Ergebnis, Zeichenzahl und HTTP-Code; die Zeile „Sprachausgabe“ der
Selbstprüfung nennt das Ergebnis der letzten Ansage. Ins Protokoll
ansage.logkommt je Ansage eine Zeile mit Art, Ergebnis, Zeichenzahl (jetzt Zeichen statt Bytes) und HTTP-Code – nie der Text, nie ein Sprechtoken. - Baustein-Liste: die Spalte „Eingänge verbinden mit“ in der Form
#Nbzw.I1 = #N, I2 = #M(für das Leitungswerkzeug). - Sprachmodul 1.1.1.
In Loxone: nichts zu tun.
Gemeinsame Sprachausgabe (Entscheidung 40, Stufe 1). Gemessen gegen Attrappen (Music Server, Alexa-NG, Chromecast 4 Lox NG) unter PHP 7.4 und 8.5 (Windows) und PHP 8.3 mit und ohne curl (WSL), dazu die Oberfläche unter PHP 7.4 und 8.5. Nicht am Gerät, nicht an einem echten Lautsprecher.
- Ansagen laufen über die gemeinsame Sprachausgabe des Hauses (
webfrontend/html/sprachausgabe.php, Fassung 1.0.2, in jedem Plugin mit Sprachausgabe dieselbe Datei). Einstellungen, Formular, Texte, Reiter Test und Sicherungsdatei bleiben, wie sie sind; eine Sicherung älterer Fassungen lässt sich weiter zurückspielen. - Der Ansagetext steht nicht mehr im Protokoll, nur seine Länge, z. B.
Ansage gesendet (37 Zeichen) -> OK. Das betrifft Music Server, MusicServer4Home und die eigene Vorlage; bei Alexa-NG und Google-Lautsprechern stand schon bisher nur die Länge da. Die Länge zählt wie in den übrigen Zeilen dieses Protokolls die Bytes (ein Umlaut zählt zwei). - Music Server und eigene Vorlage: Eine Umleitung (HTTP 3xx) wird nicht mehr verfolgt, und als gesendet gilt nur eine Antwort 2xx. Ein Proxy aus der Umgebung wird nicht benutzt. Die Wartezeit bleibt 10 s.
- Alexa-NG und Google-Lautsprecher: dieselben Antworten, Meldungen und Wartezeit 10 s. Berichtigt: Unter PHP 7.4 ohne curl konnte eine Antwort, die mitten im Rumpf abbricht, nach 12 s als gesendet gelten; jetzt ist das eine Zeitüberschreitung nach 10 s.
In Loxone: nichts zu tun.
Getrennte Geräte und ehrliche Überschriften (Verbesserungsliste Weissware-c1). Gemessen mit Attrappen für die Hersteller-Clouds, Broker und Gateway unter PHP 7.4, 8.3 und 8.5; nicht am Gerät, nicht an echten Hausgeräten.
- Getrenntes Gerät: Verliert ein Gerät die Verbindung zur Hersteller-Cloud, behält auch der HTTP-Endpunkt die zuletzt gemessenen Zustände (
ZUSTAND,LAEUFT,RESTMIN…) und meldetVERBUNDEN=0, wie es MQTT schon tat. Bisher kamen über HTTP nur Striche. Das gilt auch nach einem Neustart des Dienstes. Ein Gerät, das noch nie gemessen wurde, zeigt weiter-. - Fertig-Ansage nach Trennung: Meldet sich ein Gerät nach einer Trennung als fertig zurück, kommt die Fertig-Ansage jetzt. Bisher ging sie verloren.
- Überschrift „Der Vorgang ist nicht gelungen“: Sie steht über Fehlern von Dienstknöpfen, Testansage, Aktionen im Reiter Test, Anmeldung über den Dienst, „Log leeren“ und Zurückspielen der Zugangsdaten. Bisher stand dort „Es wurde nichts gespeichert“, obwohl nichts zu speichern war. Bei Beanstandungen im Formular bleibt „Es wurde nichts gespeichert“.
In Loxone: Wer bei einer Trennung über HTTP mit Strichen gerechnet hat, beachtet jetzt VERBUNDEN.
Durchgang mit vier Prüfern (Befunde: Pruefung-Durchgang-2026-09-29/Weissware_BEFUNDE_UND_VERBESSERUNGEN.md, Entscheidungen 1, 4, 5, 8, 16, 19, 26) und Ansage-2/Ansage-3.
Gemessen mit Attrappen für die Hersteller-Clouds, Broker, Gateway, Alexa NG und Chromecast 4 Lox NG unter PHP 7.4, 8.3 und 8.5 sowie im Installer-Prüfstand; nicht am Gerät, nicht an echten Hausgeräten.
Verhalten, das Loxone sieht
OK=0nach dem Alter. Liegt der letzte erfolgreiche Abruf mehr als dreimal den Ruhetakt zurück oder mehr als 5 s in der Zukunft, antwortenstatus,verbrauch,dienstundgeraetemitOK=0;ALTERsteht unverändert daneben. Bisher bliebOK=1stehen, auch wenn der Dienst seit Tagen tot war.- Ein schweigender Anbieter. Bei mehreren Anbietern bleiben die Geräte eines ausgefallenen Anbieters mit ihren
zuletzt gemessenen Werten stehen und antworten mit
OK=0(bisherGERAET_UNBEKANNT); die Fertigmeldung nach seiner Rückkehr kommt. Über MQTT neu je GerätgeraetN/okundgeraetN/ts(nie zurückbehalten). - Ohne gemessenen Stand antworten
statusundverbrauchmit HTTP 503GRUND=KEINE_DATEN; ein Neustart ohne Netz überschreibt die zuletzt gemessenen Werte nicht mehr. - Entfernte Geräte: Führt ein fehlerfrei antwortender Anbieter ein Gerät nicht mehr, antwortet der Endpunkt
GRUND=GERAET_ENTFERNT, und seine zurückbehaltenen MQTT-Themen bekommen einmal-(vom Broker nachgelesen). - Schaltende Befehle brauchen
geraet=(sonst HTTP 400GRUND=GERAET_FEHLT; bisher traf es still Gerät 1).ein/ausmit demselben Wert binnen 60 s werden nicht noch einmal gesendet (UNVERAENDERT=1in der Antwort, vorMELDUNG). Ein Auftrag, der älter als 60 s in der Warteschlange liegt, wird auch im laufenden Betrieb verworfen. - Parameter als Liste (
token[]=…,geraet[]=…) werden mit HTTP 400GRUND=PARAMETERabgewiesen. - MQTT ohne leere Nutzlast: Felder ohne Aussage gehen als
-hinaus (retained auf retained Themen); nach dem Programmende bleibt kein alter Programmtext, keine alte Schleuderdrehzahl und kein vergangenerfertig_umstehen. - MQTT sendet nur Geändertes; Lebenszeichen in jedem Lauf, der volle Satz alle 30 Minuten und nach dem Start.
- Präfixwechsel und „MQTT aus“: das bisherige Präfix wird vorgemerkt; der Dienst räumt die zurückbehaltenen Themen darunter ab (mit Nachlesen beim Broker), „MQTT aus“ räumt einmal ab, die Deinstallation leert auch vorgemerkte Präfixe.
Ansage
- Zwei neue Ausgabearten, ab Werk nicht gewählt: Alexa-NG (Amazon Echo) und Google-Lautsprecher über Chromecast 4 Lox NG (ab 1.3.15) – je mit eigenem Sprechtoken, Gerät und Lautstärke. Das Token steht nie in Seite, Sicherung oder Protokoll; vom Ansagetext nur die Länge. Die Testansage zeigt die Antwortzeile, der Reiter Test prüft das Token ohne Ansage. Fällt das andere Plugin aus, entfällt die Ansage.
- Fertigmeldung bei gleichnamigen Geräten kommt nur noch einmal (Merkschlüssel ist jetzt die Gerätekennung).
Oberfläche
- Kein Wiederholen mit F5: jeder Knopf leitet um (PRG); „Neues Token“, Dienstknöpfe, Testansage und Schaltknöpfe lösen beim Neuladen nichts mehr aus.
- Bei einer Beanstandung wird nichts gespeichert, alle Mängel werden genannt, die eingetippten Werte stehen markiert wieder im Formular (ohne Geheimnisse). Nichts wird mehr still geklemmt oder ersetzt (Port, Lautstärke, Ausgabeart, Sprachkürzel, Anführungszeichen in Client-Geheimnissen, Schrägstriche am MQTT-Präfix).
- Scheitert das Speichern im Reiter MQTT oder das Leeren der Logdatei, steht es auf der Seite.
- Sicherung: dieselbe Musterliste wie das Formular (IPv6, Umlaute in Zonen); „Sichern“ warnt gelb und mit
_warnung, wenn ein gespeicherter Wert beim Zurückspielen abgewiesen würde; ein leeres Aktionstoken in der Datei lässt das geltende stehen (mit Hinweis), ein Token in fremder Form wird abgewiesen; Sprechtoken sind nie enthalten. - „Anmeldung neu erzwingen“ verlangt einen Bestätigungshaken und sagt, wie es weitergeht (Anmeldung im Browser).
Betrieb und Installation
- Nie zwei Dienste: Startsperre in
dienst.shund Einzelinstanz-Sperre im Dienst (zwei Wächter in derselben Sekunde ergaben bisher zwei Dienste). - Anmeldung: eine Erneuerung ohne Zugriffstoken oder mit nicht schreibbarer
token.jsongilt als gescheitert und wird gemeldet; „invalid_grant“ heißt „bitte neu anmelden“; solange das alte Zugriffstoken gilt, wird mit ihm weitergelesen. Der Miele-Code geht über eine 0600-Datei, nie über die Befehlszeile. Zugangsdaten, Konfiguration und Formularmerkwort werden mit „Rechte vor Inhalt“ geschrieben. - Neuinstallation über Reste spielt nichts mehr ein: frühere Sicherungen gehen nach
.alt(eine<WARNING>), die Deinstallation räumt sie ab. Ein Update holt eine bewusst verworfene Anmeldung nicht mehr zurück. - Volle Karte beim Update: Sicherungen werden über eine Nebendatei angelegt und geprüft; scheitert das, bleibt die
bisherige Sicherung stehen und das Protokoll sagt es (
<WARNING>statt<OK>). - Ein Protokoll ohne Widerspruch: der zweite Aufruf von
postinstall.shauspostupgrade.shwird übersprungen. - Unter PHP 8.5 keine Abkündigungsmeldung mehr; der Wächter liest seine Grenze auch dann richtig, wenn die PHP-Kommandozeile Meldungen anzeigt.
In Loxone: Schaltende Befehle brauchen jetzt geraet=; die Ausfallerkennung auf OK prüfen.
Sammelnachzug vom 30.09.2026, sonst keine Änderung: curl_close() wird nur
noch unter PHP 7 aufgerufen. Ab PHP 8.0 wirkt der Aufruf nicht mehr, und
PHP 8.5 meldet ihn zur Laufzeit als veraltet. Bei eingeschalteter
Fehleranzeige konnte diese Meldung vor einer Antwort an Loxone landen. Am
LoxBerry mit PHP 7.4 ändert sich nichts.
Die Deinstallation kann nicht mehr hängen bleiben. Beim Leeren der
zurückbehaltenen MQTT-Themen (weissware.py --mqtt-leeren) stand eine
Zeitgrenze von 60 s, die aber nur SIGTERM schickte; ein Programm, das es nicht
annimmt, hielt die Deinstallation an, bis es von selbst endete — und die
Sicherung mit den Zugangsdaten der Herstellerclouds blieb so lange liegen. Jetzt
folgt nach weiteren 5 s SIGKILL (timeout -k 5 60). Außerdem stand ein Abbruch
nach 60 s bisher gar nicht in der Ausgabe des Installers; jetzt meldet eine
<WARNING>-Zeile, dass unter dem Präfix der Linie noch etwas im Broker stehen
kann, mit dem Befehl zum Löschen von Hand.
Gemessen in WSL (Pruefung-Weissware-0.9.34): mit einem SIGTERM-festen
Platzhalter an der Stelle von bin/weissware.py lief die Deinstallation vorher
bis zur äußeren Grenze von 150 s, ohne Meldung; jetzt endet sie nach 67 s mit
Warnung, die Sicherung ist entfernt, der Platzhalter beendet. Das echte
weissware.py nimmt SIGTERM an (sein Signalhandler wird erst hinter dem Zweig
--mqtt-leeren gesetzt) — es hätte also nicht gehangen, aber ein Abbruch nach
60 s blieb bis 0.9.33 stumm. Der root-Zweig (su loxberry) ist mit Attrappen
für id und su gemessen, nicht mit dem echten su.
Die Rückfrage beim Broker, ob früher zurückbehaltene Dienstwerte (ts, ok,
fehler_folge, ausfaelle …) noch dastehen, liest jetzt die Antwort auf das
Abonnement (SUBACK). Lehnt der Broker das Lesen ab (Rückgabe 0x80, etwa durch eine
Zugriffsregel) oder antwortet er nicht zu jedem Thema, gilt er als „nicht zu
fragen“: kein Merker „vom Broker bestätigt“, die Altwerte werden weiter unmittelbar
vor dem gültigen Wert gelöscht, das Protokoll sagt es (und nennt das abgelehnte
Abonnement als möglichen Grund), und die Deinstallation leert, statt „nichts zu
leeren“ zu melden. Bis 0.9.32 galt eine Ablehnung als „nichts belegt“ (gemessen in
WSL, Pruefung-Weissware-0.9.33, Fälle S3, S4, S7, S9, S11).
- Die Altwerte gelten erst als abgeräumt, wenn der Broker es bestätigt.
Seit 0.9.26 räumt der Dienst einmal die Themen ab, die ältere Fassungen
zurückbehalten gesendet haben (
ts,ok,fehler_folge,ausfaelle,ausfall/…) — mit einer leeren Nachricht mitretainan den UDP-Eingang des MQTT-Gateways. Bis 0.9.31 setzte er danach sofort seinen Merker „erledigt". Der UDP-Eingang verwirft unter Last aber Datagramme, ohne dass der Absender davon etwas merkt; ging die Löschung verloren, blieb der Altwert für immer im Broker, und der Dienst versuchte es nie wieder. Jetzt fragt der Dienst den Broker selbst (mit den Zugangsdaten des MQTT-Gateways aus der LoxBerry-Konfiguration, ohne zusätzliche Bibliothek), ob noch etwas dasteht. Den Merker setzt er erst, wenn der Broker sagt: nichts mehr. Bis dahin geht die Löschung in jedem Lauf mit hinaus, jeweils unmittelbar vor dem gültigen Wert desselben Themas (bisher alle am Anfang). Ein Merker aus einer früheren Fassung gilt nicht, der Broker wird einmal gefragt. Grenze: lässt sich der Broker nicht befragen (kein Port in der Konfiguration, keine Verbindung, Anmeldung abgewiesen), gibt es keinen Merker — dann geht die Löschung in jedem Lauf unmittelbar vor dem gültigen Wert hinaus, und im Protokoll steht einmal je Stunde, warum. Ohne eingetragenen Port wird nicht auf 1883 geraten. - Die Deinstallation liest beim Broker nach. Sie fragt vorher, welche Themen der Linie noch zurückbehalten stehen, schickt nur für diese die leere Nachricht und fragt nach jeder Runde erneut (höchstens drei Runden). Die Ausgabe sagt, was der Broker bestätigt hat — „nichts zu leeren", „keines steht mehr" oder welche Themen noch stehen. Lässt sich der Broker nicht befragen, geht wie bisher jedes Thema dreimal hinaus, und die Ausgabe sagt, dass nicht nachgelesen wurde.
- Ein ausgepacktes Archiv wirkt nicht mehr auf die installierte Anlage.
Lag ein Archiv unter einer echten LoxBerry-Wurzel (oder war nur
LBHOMEDIRgesetzt, wie auf jedem LoxBerry), nahmenbin/weissware.py,bin/dienst.shund die Oberfläche die Anlage: der Selbsttest legte deren Protokollordner an,dienst.sh stopnahm ihr den Merker „soll laufen", der Knopf „Dienst anhalten" rief ihr Dienstskript. Jetzt gilt die Anlage nur, wenn die Datei dort installiert liegt oderLBHOMEDIRundLBPPLUGINDIRbeide gesetzt sind; sonst steigen die Programme mit einer Meldung aus, und die Oberfläche arbeitet auf dem eigenen Ordner. Ausdrücklich gerufen verwaltetdienst.shden Dienst der Anlage, nicht den des Archivs. - Der Reiter Test reiht ohne laufenden Dienst nichts mehr ein, wie der Miniserver-Endpunkt schon bisher. Ein Auftrag trägt seinen Zeitpunkt, und der Dienst verwirft beim Start, was älter als 60 Sekunden ist — bisher lief ein so eingereihter Befehl beim nächsten Start ungefragt an.
- Die Marke „Aktualisierung läuft" verträgt eine Uhr, die zurückspringt. Bis zu 300 Sekunden „aus der Zukunft" gilt sie noch (bisher: jede Sekunde Zukunft machte sie ungültig).
Gemessen in WSL, nicht am Gerät und nicht an einem echten Broker (an seiner Stelle ein Nachbau von Gateway-Eingang und Broker, der auch verlorene Löschungen spielen kann): 58 Prüffälle, vorher 35 rot, nachher alle grün.
- Nach einem Upgrade verlangt das Installationsprotokoll die Zugangsdaten
nicht mehr neu. Bis 0.9.30 endete es jedes Mal mit den „Naechsten
Schritten" samt „Zugangsdaten eintragen", auch wenn sie gerade
zurückgespielt worden waren. Jetzt erscheint die Anleitung nur, wenn
zugang.jsondanach kein Geheimnis trägt (dieselbe Prüfung wie für die Sicherung:hc_client_secret,miele_client_secretoderst_token), sonst „Aktualisierung abgeschlossen, Einstellungen übernommen".
- Die Kachel „MQTT" zeigt jetzt, ob dieses Plugin veröffentlicht. Bis 0.9.29 stand dort als großer Wert der Autostart des MQTT-Gateways von LoxBerry, und „MQTT ein" las sich, als sende das Plugin — auch wenn es im Reiter MQTT ausgeschaltet war. Der Autostart des Gateways steht jetzt klein darunter; fehlt der MQTT-Abschnitt in der LoxBerry-Konfiguration, heißt er dort „nicht feststellbar" statt „aus".
- Die Deinstallation leert die zurückbehaltenen (retained) MQTT-Themen.
Bis 0.9.29 blieben die Gerätezustände (
geraete,geraetN/…) nach dem Entfernen des Plugins im Broker stehen, dazu, was ältere Fassungen zurückbehalten gesendet hatten (ok,ts,fehler_folge,ausfaelle,ausfall/…), und der Miniserver bekam sie nach jedem Neustart von Broker oder Gateway wieder. Jetzt schicktuninstall— nach dem Anhalten des Dienstes — für jedes dieser Themen eine leere Nachricht mitretainan den UDP-Eingang des MQTT-Gateways, für jede je vergebene Gerätenummer, unter dem eingestellten Themenpräfix. Themen, die nie zurückbehalten gesendet wurden (Restzeit, Fortschritt, Verbrauch, Temperatur), bleiben unberührt. Grenze: das ist der einzige Weg, den dieses Plugin zum Broker hat, und er bestätigt nichts. Der UDP-Eingang des Gateways verwirft unter Last Datagramme, und ob eine Löschung ankam, lässt sich darüber nicht nachlesen. Jede Löschung geht deshalb dreimal hinaus, mit einer Sekunde Abstand; das senkt den Verlust, schließt ihn aber nicht aus. Was danach noch im Broker steht, lässt sich mitmosquitto_pub -r -n -t <thema>von Hand löschen. Ein früher benutztes, später geändertes Themenpräfix wird nicht mehr erfasst. Das gilt ebenso für das einmalige Abräumen der Altwerte im Betrieb (seit 0.9.26): der Dienst setzt seinen Merker, sobald die Löschung ohne Fehler gesendet ist — angekommen sein muss sie deshalb nicht. - Die eigene Fassungsnummer wird gelesen, nicht mehr eingetragen. Der
Dienst schickte an Home Connect, Miele und SmartThings seit 0.9.19 den
User-Agent
LoxBerry-Weissware/0.9.18— die Nummer stand als Konstante im Quelltext, und das Werkzeug, das beim Hochsetzen die Fassung nachzieht, kennt diese Stelle nicht. Jetzt liest der Dienst sie aus der Plugin-Datenbank des LoxBerry (über den Ordnernamen), im ausgepackten Archiv aus derplugin.cfg; findet er keine, geht der User-Agent ohne Nummer hinaus. Die Selbstprüfung nennt Fassung und Quelle. - Die Deinstallation meldet keinen Prozess mehr, der während der Suche
endet. Im Installationsprotokoll stand dann
„
/proc/<n>/cmdline: No such file or directory"; dieselbe Stelle gab es inbin/dienst.sh(die Meldung erschien dort in der Oberfläche) und im Rückfallweg vonpreupgrade.sh. - Ohne gefundene LoxBerry-Wurzel liest die Oberfläche nur ihre eigenen
Dateien. Die Sprachtexte (
ww_t()) und eine Prüfzeile im Reiter Test bildeten bis 0.9.29 den Installationspfad auch ohne Wurzel und fragten ihn ab — also/templates/plugins/html/langund/webfrontend/html/plugins/weissware/index.phpab der Laufwerkswurzel. Lag dort etwas, zeigte die Oberfläche fremde Texte. Auf einem LoxBerry gibt es die Wurzel immer; betroffen war das ausgepackte Archiv.
Gemessen in WSL, nicht am Gerät und nicht an einem echten Broker (UDP-Horcher an der Stelle des Gateway-Eingangs): 33 Prüfzeilen, vorher 23 rot, nachher alle grün; 20 Rückbauten geeicht; die Prüfstände von 0.9.29 bleiben grün.
Die Oberfläche fällt ohne gefundene Wurzel nicht mehr auf einen festen
Standardort zurück. Die Bibliothek der Oberfläche (ww_paths() und die
Sprachtexte in ww_t()) nahm bis 0.9.28 ein gesetztes LBHOMEDIR, sobald es
ein Verzeichnis war, und fand die Suche keine Wurzel, versuchte sie noch
/home/loxberry/loxberry. Auf einem LoxBerry liegt dort keine Wurzel — die
Wurzel ist /home/loxberry selbst. In WSL Ubuntu nachgestellt
(19.09.2026, Pruefung-Weissware-0.9.29, messe_h2.sh; der fremde Baum nur in
einem eigenen Mount-Namensraum): aus einem ausgepackten Archiv heraus las die
Oberfläche die Konfiguration eines fremden Baums unter diesem Pfad, schrieb
dort aus dessen Zweitschrift eine weissware.json und zeigte dessen
Sprachtexte (Fälle B1 bis B4, B7); ein LBHOMEDIR, das auf nichts zeigte,
blieb als Wurzel stehen (B5), ein beliebiges Verzeichnis wurde Wurzel (B6,
B8). Jetzt gilt LBHOMEDIR nur mit config/plugins darunter, sonst sucht die
Oberfläche aufwärts nach config/system/general.json; findet sie nichts,
arbeitet sie auf dem eigenen Ordner. Installiert findet sie ihre Wurzel wie
bisher, mit und ohne LBHOMEDIR (A1, A2).
Dieselbe Regel in Dienst, Deinstallation und Installationsskripten.
bin/dienst.sh und bin/weissware.py nahmen ebenfalls jedes Verzeichnis als
LBHOMEDIR — dienst.sh start legte darin data/plugins/weissware an (S1),
weissware.py arbeitete darin (P1); uninstall/uninstall entfernte dort Marke
und soll_laufen und endete mit 0 (U1). postinstall.sh rechnete ohne
fünftes Argument und ohne LBHOMEDIR zwei Ebenen über seinem Ablageort,
ungeprüft: unter einem fremden Baum ohne general.json richtete es dort
Daten-, Protokoll- und Konfigurationsordner ein und stellte eine
weissware.json aus dessen Sicherung wieder her (I1); tief unter einer echten
Wurzel richtete es alles an der falschen Stelle ein (I3). preupgrade.sh lief
ohne Wurzel mit Pfaden ab / weiter (V2) und legte bei einem beliebigen
LBHOMEDIR dort seine Marke an (V1). Alle prüfen jetzt config/plugins,
suchen sonst aufwärts mit general.json und tun ohne Wurzel nichts
(dienst.sh, weissware.py: Meldung und Abbruch; die drei Skripte:
<WARNING> und Rückgabe 1). Beim Installieren, Aktualisieren und
Deinstallieren über LoxBerry ändert sich nichts — der Installer übergibt die
Wurzel als fünftes Argument (I2, V3).
Aussagen über den Zustand des Dienstes gehen nicht mehr zurückbehalten
(retained) hinaus. Der Hausherr hat am 19.09.2026 entschieden: neben ok
auch fehler_folge, ausfaelle und ausfall/homeconnect, ausfall/miele,
ausfall/smartthings. Am UDP-Eingang gemessen (Fall R1 in messe_retain.sh):
bis 0.9.28 retain weissware/fehler_folge 0, retain weissware/ausfall/miele 0
und so fort — zurückbehalten stünde „keine Fehlversuche, kein Ausfall" auch
dann noch im Broker, wenn der Dienst längst nicht mehr läuft. Geändert in
beiden Tabellen (RETAIN in bin/weissware.py, ww_mqtt_retain() der
Oberfläche); die Zeile im Reiter Test heißt jetzt „Gehen Lebenszeichen und
Dienstzustand nie zurückbehalten hinaus?" und hält alle sieben Themen fest.
Die Altwerte werden einmal abgeräumt — je Thema eine leere Nutzlast mit
retain, der gültige Wert unmittelbar dahinter; alle drei ausfall/*, auch
für einen nicht eingerichteten Anbieter, weil ein Altwert aus einer früheren
Einrichtung stehen kann. Ein Merker aus 0.9.28 (weissware ts,ok) überspringt
das nicht (R3); ts und ok gehen dabei ein zweites Mal leer hinaus. Die
Gerätezustände (geraete, geraetN/…) bleiben retained. Der Preis: nach
einem Neustart von Broker oder Gateway fehlen die Dienstzustände, bis der
Dienst wieder sendet (bis zum Ruhetakt, ab Werk 300 s). Ob das Gateway der
Anlage die leere Nachricht als Löschung weitergibt, ist weiterhin nicht am
Gerät gemessen.
Gemessen in WSL, nicht am Gerät: 33 und 25 Prüfzeilen, vorher 19 und 19 rot, nachher alle grün; 13 Rückbauten geeicht, jeder genau an seinen Zeilen rot; die Prüfstände von 0.9.28 bleiben grün (17 und 24 Zeilen).
Die Suche nach der LoxBerry-Wurzel verlangt jetzt config/system/general.json,
und ohne Wurzel wird nichts mehr geraten. Wird bin/dienst.sh,
bin/weissware.py oder die Bibliothek der Oberfläche ohne LBHOMEDIR
aufgerufen, sucht sie die Wurzel vom eigenen Ablageort aufwärts. Bis 0.9.27
galt dabei jedes Verzeichnis mit config/plugins und webfrontend als
Wurzel, und fand die Suche keines, rechneten dienst.sh und weissware.py
eine feste Zahl Ebenen über sich. Solche Ordner liegen auf einem Prüfrechner
auch außerhalb eines LoxBerry, etwa als Reste früherer Prüfläufe. In WSL Ubuntu
nachgestellt (18.09.2026, Pruefung-Weissware-0.9.28, Fälle F1 bis F6) — in
einem solchen Baum ohne general.json:
bin/dienst.sh startlegtedata/plugins/weisswareundlog/plugins/weisswarean,stoplöschtesoll_laufen;- der Dienst und die Oberfläche hielten den Baum für die Wurzel und hätten dort gelesen und geschrieben.
Die drei Suchen prüfen jetzt zusätzlich config/system/general.json — ein
LoxBerry hat sie immer. general.json allein genügte in dienst.sh und
weissware.py nicht: der Rückfall auf die feste Ebenenzahl führte in denselben
fremden Baum zurück — installiert genau auf dessen Wurzel, aus einem Archiv
darin auf einen Ordner darin. Er ist entfallen; ohne
Wurzel melden beide „Es wurde kein LoxBerry-Wurzelverzeichnis gefunden" und
legen nichts an. In der installierten Lage (mit general.json) finden alle
drei die Wurzel weiterhin, auch ohne LBHOMEDIR und aus / aufgerufen (Fälle
G1 bis G4); ein gesetztes LBHOMEDIR gilt unverändert (U1 bis U3). Am Gerät
ändert sich damit nichts.
Dieselbe Regel gilt jetzt in uninstall/uninstall. Ohne fünftes Argument
und ohne LBHOMEDIR rechnete das Skript bis 0.9.27 drei Ebenen über seinem
Ablageort und prüfte nichts. In WSL nachgestellt (Fall D1): abgelegt unter
<fremder Baum>/pruef/weissware/uninstall/, löschte es in dem fremden Baum
(ohne general.json) die Sicherung weissware.backup.zugang.json, die
Upgrade-Marke und soll_laufen und meldete „Sicherung entfernt". Jetzt sucht
es aufwärts nach config/plugins, data/plugins und
config/system/general.json; ohne Wurzel meldet es <WARNING>, beendet und
entfernt nichts und endet mit 1. Beim Deinstallieren über LoxBerry ändert
sich nichts — der Installer übergibt die Wurzel als fünftes Argument (Fall D3),
und abgelegt unter data/system/uninstall/ findet die Suche sie auch ohne
(Fall D2). Ein laufender Dienst wird weiterhin beendet, der einer anderen
Installation nicht (Fall D6).
weissware/ok geht nicht mehr zurückbehalten (retained) hinaus. Am
UDP-Eingang gemessen (Fall R1): bis 0.9.27 retain weissware/ok 1. Der
Hausherr hat am 18.09.2026 entschieden, dass ok nie retained ist — ein
zurückbehaltenes ok=1 bliebe stehen, wenn der Dienst stirbt, und nach einem
Neustart von Broker oder Gateway läse Loxone „in Ordnung" von einem Dienst,
der nicht mehr läuft. Der Preis: nach einem solchen Neustart fehlt ok, bis
der Dienst wieder sendet (bis zum Ruhetakt, ab Werk 300 s). Geändert in beiden
Tabellen (RETAIN in bin/weissware.py, ww_mqtt_retain() der Oberfläche);
die Zeile „Geht das Lebenszeichen nie zurückbehalten hinaus?" im Reiter
Test hält jetzt ts und ok fest. Der alte zurückbehaltene Wert wird
wie ts in 0.9.26 einmal abgeräumt — eine leere Nutzlast mit retain auf
<präfix>/ok, der gültige Wert unmittelbar dahinter. Der Merker
retain_ts_geraeumt trägt dafür jetzt Präfix und Themenliste
(weissware ts,ok); ein Merker aus 0.9.26/0.9.27 trägt nur das Präfix und
hätte das Abräumen von ok übersprungen (Fall R3). Ob das Gateway der Anlage
die leere Nachricht als Löschung an den Broker weitergibt, ist weiterhin nicht
am Gerät gemessen. Ein Alter sendet die Linie über MQTT nicht, einen Letzten
Willen hat sie nicht (sie spricht über den UDP-Eingang des Gateways, nicht
selbst mit dem Broker). Unverändert retained bleiben die Zustände, darunter
fehler_folge und die Ausfallmerker. Gemessen in WSL, nicht am Gerät
(Pruefung-Weissware-0.9.28, messe_teil2.sh, 24 Prüfzeilen, sieben
Rückbauten geeicht).
Die Upgrade-Marke, nachgerüstet nach der Bauweise aus Regeln/06 (Einspeisebremse
0.9.20, Govee 0.9.19), und die Dienste ohne PID-Datei. Alles in WSL/Ubuntu
gemessen, mit nachgebautem Installationsablauf (preupgrade, Abräumen von
config/, data/, bin/ samt venv, templates/ und webfrontend/, neue
Dateien und Cron-Datei, postinstall mit wartendem pip, postupgrade), vor und
nach der Korrektur, jede Korrektur einzeln durch Rückbau geeicht
(Pruefung-Weissware-0.9.27/, 47 Prüfzeilen). Nichts davon ist am Gerät
gemessen.
Was in der Lücke geschah. In der eigentlichen Lücke zwischen dem Abräumen
und postinstall.sh startet nichts: der Minutentakt findet soll_laufen nicht
mehr, und der Knopf „Dienst starten" scheitert an der fehlenden venv. Die Lücke
reicht aber bis in postinstall.sh hinein, und dort wurde gemessen:
- Ein bewusst angehaltener Dienst lief nach dem Upgrade wieder. Wer im
Fenster, in dem
postinstall.shdie Bibliothek installiert, auf „Dienst starten" drückte, bekam „Start fehlgeschlagen" (requests fehlte noch) — abersoll_laufenwar schon angelegt, und der Minutentakt startete den Dienst danach trotzdem. - Zwei oder drei Dienste nach dem Neustart in
postinstall.sh. Zwischen dem Anlegen vonsoll_laufenund dem Schreiben der PID-Datei kann der Minutentakt denselben Dienst ein zweites Mal starten. Gegen eine Wächterschleife ohne Pause: in 3 von 200 Durchgängen ohne Marke, in 1 von 100 mit einer Marke, die vor dem Start fällt, in 0 von 100 mit einer Marke, die nach dem Start fällt. Im Betrieb läuft der Wächter einmal je Minute — der Fall ist dort selten, aber möglich. - Ein Dienst ohne PID-Datei überlebte das Upgrade.
preupgrade.shhielt nur den Dienst aus der PID-Datei an,dienst.sh stopebenso. Lief einer ohne sie (von Hand gestartet, oder mit dem Knopf der alten Fassung zwischenpreupgrade.shund dem Abräumen), liefen nach dem Upgrade zwei — zwei Abrufe mit derselben Anmeldung gegen dieselben Herstellerclouds.
Was jetzt gilt.
preupgrade.shlegt als Erstesdata/plugins/<ordner>.upgrade_laeuftmit der Unixzeit an — neben dem Datenordner, der beim Upgrade abgeräumt wird.bin/dienst.shstartet nicht, solange die Marke höchstens eine Stunde alt ist, und fragt das vor dem Anlegen vonsoll_laufen. Eine ältere, leere, unlesbare oder in der Zukunft liegende Marke gilt nicht; ohne lesbare Uhr gilt sie (der Schutz fällt geschlossen aus). Anhalten bleibt jederzeit möglich.postinstall.shhält bei liegender Marke zuerst jeden eigenen Dienst an, startet dann den Dienst, falls er vor dem Upgrade lief, mit der AusnahmeWW_START_TROTZ_MARKE=1, und entfernt die Marke erst beim Verlassen (trapauf EXIT) — also nach dem Start, und auch dann, wenn es vorzeitig mit einem Fehler aussteigt.dienst.sh stopundpreupgrade.shbeenden auch Dienste ohne PID-Datei. Erkannt wird argumentweise über/proc/<pid>/cmdline: ein Python, genau diesesweissware.py, kein drittes Argument (ein Einmallauf wie--selbsttestist kein Dienst), und der Prozess gehört dem Dienstbenutzer. Ein Dienst einer Nachbarinstallation, ein Einmallauf und eintail -fauf die Datei bleiben unberührt (gemessen).uninstallräumt die Marke weg.- Der Reiter Test zeigt eine Zeile „Läuft gerade eine Aktualisierung dieses Plugins?" — mit einem Kreuz, wenn eine alte Marke liegengeblieben ist.
Die Oberfläche wird nicht gesperrt. Gemessen wurde ein Seitenaufruf und ein Speichern in der Lücke und im pip-Fenster, dazu der Minutenlauf der Ansage: Aktionstoken, Client-Geheimnis, Anmeldung und die gespeicherte Änderung waren nach dem Upgrade in jedem Fall erhalten. Eine Sperre ohne gemessenen Verlust nähme dem Anwender nur die Seite (Regeln/06).
Drei Befunde aus der Bestandsmessung vom 18.09.2026, alle in WSL/Ubuntu
nachgestellt, vor und nach der Korrektur gemessen und jede Korrektur einzeln
durch Rückbau geeicht (Pruefung-Weissware-0.9.26/, 24 Prüfzeilen, 9
Rückbaufälle). Nichts davon ist am Gerät gemessen.
Das Lebenszeichen ts ging zurückbehalten (retained) hinaus. Am
UDP-Eingang gemessen: retain weissware/ts 1789…. Der Hausstandard ist
eindeutig — das Lebenszeichen ist nie retained, sonst liefert der Broker nach
einem Neustart einen Zeitstempel aus, zu dem längst kein Dienst mehr läuft.
ts geht jetzt mit publish hinaus, in beiden Tabellen (RETAIN in
bin/weissware.py, ww_mqtt_retain() in der Oberfläche). Der Preis ist
benannt: nach einem Neustart des Miniservers steht ts erst mit dem nächsten
Takt wieder an (bis zum Ruhetakt, ab Werk 300 s).
Der alte zurückbehaltene Wert verschwindet davon nicht von selbst. Der Dienst
räumt ihn deshalb einmal ab — eine leere Nutzlast mit retain auf
<präfix>/ts, der gültige Wert unmittelbar dahinter — und merkt sich das
im Datenordner (retain_ts_geraeumt, Inhalt: das Themenpräfix; wer das
Präfix umstellt, bekommt die Abräumung unter dem neuen Stamm noch einmal).
Ob das Gateway der Anlage die leere Nachricht als Löschung an den Broker
weitergibt, ist nicht gemessen.
Neu im Reiter Test: „Geht das Lebenszeichen nie zurückbehalten hinaus?" Die vorhandene Zeile „Stimmen die beiden Retain-Tabellen überein?" hielt die Tabellen nur gegeneinander — beide sagten dasselbe, und beide sagten es falsch.
Unverändert und ausdrücklich nicht angefasst: weissware/ok geht weiter
retained hinaus. Ob ok zum Lebenszeichen gehört, ist in den Hausregeln
widersprüchlich festgehalten; die Entscheidung steht aus. Ebenso bleibt
geraetN/laeuft retained — es ist der Zustand des Geräts, nicht des Dienstes.
Eine abgeschnittene Datendatei verhinderte die Rückholung nach dem Update.
postinstall.sh holte token.json, geraetenummern.json und laeufe.json
nur zurück, wenn die Datei leer war ([ ! -s … ]). Eine halb geschriebene
Datei ist nicht leer: die Anmeldung an drei Herstellerclouds blieb fort, und
die Gerätenummern — also die Adressen in Loxone — wurden neu vergeben.
Entschieden wird jetzt nach dem Inhalt (ww_json_traegt(): lesbares JSON mit
mindestens einem Eintrag); der verdrängte Stand liegt als <datei>.kaputt
(0600) daneben. Dieselbe Lücke saß auf der anderen Seite: preupgrade.sh
kopierte die drei Dateien unbedingt über die Sicherung, eine abgeschnittene
token.json verdrängte also die letzte heile Abschrift. Beide Skripte stellen
jetzt dieselbe Frage.
bin/dienst.sh und bin/weissware.py rieten Wurzel und Ordnernamen.
Beide rechneten sie aus dem eigenen Ablageort und übergingen ein gesetztes
$LBHOMEDIR. Nachgestellt: dienst.sh status aus einem Prüfarchiv unter
<Wurzel>/pruefung/<plugin>/bin legte in der laufenden Installation
data/plugins/bin und log/plugins/bin an. Jetzt gilt die Reihenfolge
Umgebung ($LBHOMEDIR, $LBPPLUGINDIR) → Aufwärtssuche → Ablageort, wie in
der Oberfläche schon lange. Außerdem legt dienst.sh Daten- und Logordner
nur noch beim Start an, nicht mehr bei jedem Aufruf: vorher legte schon
ein status den eben abgeräumten Datenordner wieder an.
Eine abgeschnittene weissware.json kostete das Aktionstoken — und mit ihm
jede Adresse im Miniserver. Gemessen am 18.09.2026 in WSL/Ubuntu unter
PHP 8.3.6 und unter Windows-PHP 7.4.33 und 8.4.24
(Pruefung-Weissware-0.9.25/).
Die Selbstheilung entschied nach der Form der Datei:
if ($erzeugen && ($roh === '' || $roh === '{}') && is_file($p['sicherung'])) {Eine halb geschriebene Datei — Stromausfall, volle Speicherkarte,
Handbearbeitung — ist weder leer noch {}. Sie ging an dieser Zeile vorbei,
json_decode gab null, die Konfiguration bestand nur noch aus den
Werkseinstellungen, ww_token() würfelte ein neues Aktionstoken, und
ww_config_speichern() kopierte es über die Zweitschrift. Danach antwortet
jeder virtuelle Eingang im Miniserver mit HTTP 403, und das alte Token lässt
sich nicht zurückrechnen.
Entschieden wird jetzt nach dem Inhalt: trägt die Datei noch ein
Aktionstoken? Der Entscheid steht in einer Funktion (ww_selbstheilung()),
die alle vier Wege benutzen — Oberfläche, Speichern, unangemeldeter Endpunkt
und der Minutenlauf aus cron/cron.01min. Geheilt wird nur aus einer
Zweitschrift, die selbst ein Token trägt; der verdrängte Inhalt wird nicht
weggeworfen, sondern liegt als weissware.json.kaputt daneben (Rechte 0600 —
in der Konfiguration steht das Aktionstoken). Gemeldet wird einmal, nicht bei
jedem Minutenlauf.
Dazu drei Wege, die an der Heilung vorbeischreiben konnten:
- Die Zweitschrift wurde bisher mitgezogen, sobald der neue Stand
irgendein nicht leeres Token trug. Verglichen wird jetzt mit dem, was die
Zweitschrift selbst führt (
ww_zweitschrift_ziehen()): ein Stand ohne das, was dort steht, ersetzt sie nicht. Gespeichert wird trotzdem — nur der Rückweg bleibt stehen, und das Protokoll sagt es. - Die Tokenerzeugung schrieb durch diese Wache hindurch, weil ein frisch
gewürfeltes Token ein gültiger Wert ist. Ein neues Token entsteht deshalb
nur noch, wenn keine Zweitschrift mit Token danebenliegt
(
ww_token_gesperrt()). Sonst erscheint eine Meldung, es wird nichts geschrieben, und die nächste gelungene Selbstheilung holt das alte Token zurück. Derselbe Schutz sperrt den Knopf „Neues Token", solange die Konfiguration unlesbar ist. preupgrade.shkopierte die Konfiguration ungeprüft über die Zweitschrift — bei einem Auto-Update mit beschädigter Datei war das der sichere Verlust.postinstall.shholte die Sicherung nach demselben Formentscheid zurück wie die Bibliothek und ließ die abgeschnittene Datei deshalb stehen. Beide fragen jetzt nach dem Inhalt, und beide lesen die Datei wirklich (php -r, keingrep): eine abgeschnittene Datei trägt den Text"aktionstoken":"…"weiterhin.
Neu im Reiter Test: „War die Konfiguration heil, als diese Seite aufgebaut wurde?" Die Zeile merkt sich die zuerst festgestellte Lage, bevor die Selbstheilung sie beseitigt — sonst meldete sie „in Ordnung" für eine Datei, die beim selben Seitenaufruf beschädigt war.
Was diese Fassung nicht misst: nichts davon ist am Gerät gemessen, sondern über die PHP-Kommandozeile gegen eine LoxBerry-Nachbildung; und es wurde kein Hausgerät und keine Cloud angesprochen.
Der Wächter startete den gesunden Dienst neu — und hob dabei die Fehlerbremse auf. Am Gerät gemessen:
Waechter: der Dienst laeuft, hat aber seit 1508 s kein Abbild geschrieben
(Grenze 1500 s) - Neustart.
Zwei Zahlen aus zwei Stellen, die nichts voneinander wussten:
| Formel | bei takt_ruhe = 300 |
|
|---|---|---|
| Wächtergrenze | max(180, 5 × takt_ruhe) |
1500 s |
| Fehlerbremse | min(3600, takt_ruhe × min(8, fehler_folge)) |
ab 5 Fehlversuchen 1500 s, dann 1800, 2100 … |
Der Wächter misst, was der Dienst hinterlässt — das Abbild, und das entsteht nur nach einem abgeschlossenen Durchlauf. Sobald die Bremse die Grenze erreichte, schlief der Dienst planmäßig länger, als der Wächter Stillstand duldete.
Schlimmer als der überflüssige Neustart: er setzt fehler_folge zurück.
Die Bremse, die eine ausgefallene Anbieter-Schnittstelle schonen soll
(Ratengrenzen, HTTP 429), wurde damit im Takt der Wächtergrenze aufgehoben
und konnte ihren Zweck nie erreichen. Ausgelöst hat es ein ganz
gewöhnlicher Anlass: das Plugin war eine Stunde lang nicht angemeldet.
Der Dienst sagt jetzt, bis wann er absichtlich pausiert. Er schreibt
pause_bis in seinen Zustand, und der Wächter liest es über die neue
Auskunft ww_pause_bis(). Damit kann er unterscheiden, was er vorher nicht
unterscheiden konnte: schläft mit Absicht gegen hängt. Gehalten wird
weiterhin beides — ein wirklich stehengebliebener Dienst wird neu gestartet
wie bisher.
Drei Vorsichtsmaßnahmen, jede gegen einen Fehler, den es im Haus schon gegeben hat:
pause_bisgeht bei jedem Zustandsschreiben mit, auch als0.zustand_schreiben()mischt in den Bestand — ein weggelassenes Feld bliebe stehen, und eine liegengebliebene Pause hätte den Wächter dauerhaft stillgelegt. Genau diese Falle hatte 0.9.7 mit der Ausfallliste.- Eine Pause, die weiter als 3900 s in der Zukunft liegt, wird nicht geglaubt: der Dienst deckelt seine Bremse selbst bei 3600 s. Ein Wächter, den eine einzige verdorbene Zahl dauerhaft stilllegen kann, ist keiner.
- Fehlt das Feld ganz — also bei einer Zustandsdatei aus 0.9.22 oder älter —, verhält sich der Wächter wie bisher.
Der Wächter fragt erst dann nach der Pause, wenn ohnehin ein Neustart anstünde; er läuft minütlich, und ein zweiter PHP-Aufruf je Minute wäre Verschwendung für einen Fall, der fast nie eintritt.
Am Gerät in beide Richtungen geeicht (Wegwerfbaum unter /tmp, echter
Vorgang als laufender Dienst), sechs Fälle, alle wie vorher festgelegt:
altes Abbild mit echter Pause → kein Neustart; mit pause_bis = 0, mit
verdorbenem Wert und mit fehlendem Feld → Neustart; frisches Abbild mit und
ohne Pause → nichts.
Geprüft gegen PHP 7.4.33 und 8.4.24, bash -n für dienst.sh. Freigabetor:
17 Prüfungen, 0 Beanstandungen.
Die Home-Connect-Anmeldung war zum Abtippen gebaut — und daran gescheitert. Bis 0.9.21 zeigte die Oberfläche den Benutzercode und die Adresse in zwei Tabellenzellen. Wer den Code mit der Maus markierte und dabei über die Zellgrenze geriet, kopierte einen Tabulator mit; Home Connect wies die Adresse dann ab:
{"error":"invalid_request","error_description":"Illegal URI reference:
Invalid input '\t', expected query-char … ?user_code=\tI5JI-9119"}
Dabei liefert Home Connect die fertige Adresse mit dem Code darin
(verification_uri_complete), und der Dienst legte sie auch ab — angezeigt
wurde sie nie. Jetzt steht sie als anklickbarer Verweis in der Tabelle:
ein Klick, kein Kopieren. Angezeigt wird sie nur, wenn sie mit http:// oder
https:// beginnt — eine Adresse aus fremder Hand gehört geprüft, bevor sie
in ein href geht. Zusätzlich streift der Dienst Leerraum von Code und
Adressen ab, bevor er sie ablegt.
Das Client Secret ist bei Home Connect optional — jetzt behandelt das Plugin
es auch so. Eine Anwendung, die im Entwicklerportal mit dem Device Flow
angelegt wurde, ist ein öffentlicher Client: sie bekommt eine Client-ID und
kein Geheimnis. Die Anmeldung kam damit immer schon aus; die Erneuerung
des Zugriffstokens schickte client_secret aber bedingungslos mit, bei leerem
Feld also client_secret=. Eine leere Client-Anmeldung ist etwas anderes als
gar keine und kann abgewiesen werden — das Plugin hätte rund einen Tag
gearbeitet und dann aufgehört, Token zu erneuern. Jetzt geht das Geheimnis nur
mit, wenn eines hinterlegt ist; dafür geht die Client-ID immer mit, wie es für
einen öffentlichen Client vorgesehen ist. Für Anwendungen mit Geheimnis
ändert sich nichts.
Abgeleitet, nicht gemessen. Zu dieser Linie gibt es hier kein Home-Connect-Konto. Dass ein leeres
client_secretabgewiesen wird, ist nicht nachgemessen — der neue Weg ist aber in jedem Fall der sichere: ein weggelassener optionaler Parameter ist nie schlechter als ein leerer.
Geprüft gegen PHP 7.4.33 und 8.4.24, Sprachdateien 553 Schlüssel DE/EN deckungsgleich. Freigabetor: 17 Prüfungen, 0 Beanstandungen.
Zustände gehen jetzt zurückbehalten (retained) an den Broker. Bis 0.9.20
sendete dieses Plugin alles mit dem Befehlswort publish. Nach einem
Neustart des Miniservers oder des MQTT-Gateways stand in Loxone deshalb bis
zum nächsten Takt — bei Ruhetakt bis zu 300 Sekunden — der alte Wert, und
ein virtueller Eingang zeigt einen alten Wert genauso an wie einen frischen.
Der Hausstandard (Regeln/07, seit 03.09.2026) lautet: Zustände zurückbehalten, Messwerte mit Zeitbezug nicht, das Lebenszeichen nie. Die Entscheidung fällt je Themenstamm in einer Tabelle, nicht am einzelnen Aufruf — ein Aufruf, der Zustand und Lebenszeichen zusammen verschickt, kann nur eines von beiden richtig machen.
23 von 30 Themenstämmen sind zurückbehalten. Nicht zurückbehalten sind die sieben, die von selbst altern:
| Thema | warum nicht |
|---|---|
geraetN/fortschritt |
wächst mit der Zeit |
geraetN/restzeit_min |
eine Dauer — 60 Minuten Restzeit, eine Woche aufbewahrt, wären eine stille Falschaussage |
geraetN/startzeit_min |
Dauer |
geraetN/laufzeit_min |
Dauer |
geraetN/energie_kwh |
Momentanwert des laufenden Programms, kein Zählerstand — nach dem Quittieren ist er fort |
geraetN/wasser_l |
ebenso |
geraetN/temperatur |
Messwert |
Zwei Entscheidungen, die nicht auf der Hand liegen, und ihre Begründung:
tsist zurückbehalten. Das ist kein Lebenszeichen „ich lief gerade": der Wert wandert nur bei einem erfolgreichen Abruf weiter und sagt damit genau, wann zuletzt gemessen wurde. Ein absoluter Zeitpunkt kann nicht „aktuell erscheinen"; ohne ihn könnte Loxone nach einem Neustart das Alter gar nicht rechnen, und ein toter Dienst wäre von einem gesunden nicht zu unterscheiden.geraetN/fertig_umist aus demselben Grund zurückbehalten.geraetN/schleuderdrehzahlist zurückbehalten — sie ist keine Messung, sondern eine Einstellung des gewählten Programms.
Ein leerer Wert geht immer als publish hinaus, egal was die Tabelle
sagt: eine leere Nutzlast mit Retain löscht das Thema im Broker.
Die Tabelle steht an zwei Stellen — ww_mqtt_retain() in der Oberfläche und
RETAIN in bin/weissware.py —, weil die eine sie anzeigen und die andere
sie anwenden muss. Zwei Tabellen sind zwei Wahrheiten, deshalb hält die neue
Zeile „Stimmen die beiden Retain-Tabellen überein?" im Reiter Test beide
samt der Themenliste gegeneinander; sie ist durch Rückbau in beide Richtungen
geeicht (ein entfernter Eintrag und ein gekippter Wert machen sie rot und
nennen den Namen). Die Themenliste im Reiter MQTT hat eine Spalte Retain
bekommen.
Was das für bestehende Anlagen heißt: nichts, was jemand einstellen müsste. Beim ersten Durchlauf nach dem Update schreibt der Dienst die Zustände zurückbehalten in den Broker; ab da überstehen sie einen Neustart. Alte, nicht zurückbehaltene Werte verschwinden von selbst.
Nicht am Gerät gemessen: dass die zurückbehaltenen Themen wirklich im Broker stehen. Der UDP-Eingang des Gateways verwirft an dieser Anlage in Stößen; „Thema X ist nicht angekommen" ist dort kein Beweis (Regeln/07).
Das Installationsprotokoll behauptete, einen Dienst angehalten zu haben, der
gar nicht lief. „Laufender Dienst angehalten." stand bedingungslos hinter
dienst.sh stop || true; anhalten() gibt ohne laufenden Dienst „laeuft
nicht" und 0 zurück, und die Antwort ging nach /dev/null. Die Meldung hängt
jetzt am Merker backup.lief, den der Abschnitt darüber ohnehin setzt — und
der auch darüber entscheidet, ob postinstall.sh den Dienst wieder startet.
Geprüft mit Werkzeuge/preupgrade_meldung_pruefen.py: gegen 0.9.20 grün, gegen
0.9.19 rot. Am Verhalten ändert sich nichts.
-
Der Reiter Test sagt jetzt, ob die MQTT-Veröffentlichung dieses Plugins eingeschaltet ist. Bis 0.9.18 stand dort nur der Zustand des MQTT-Gateways von LoxBerry — das ist eine Aussage über den LoxBerry, nicht über dieses Plugin. Wer die Veröffentlichung ausgeschaltet hatte, sah trotzdem einen grünen Haken und konnte am Reiter nicht erkennen, dass nichts an den Broker geht. Die neue Zeile steht vor der Gateway-Zeile und ist grau, wenn ausgeschaltet — das ist eine Entscheidung, kein Fehler. Anlass: derselbe Befund an BatterieBMS 0.9.17, dort am Gerät gemessen (
Regeln/04). -
Das Auswahlfeld zeichnet seinen Pfeil selbst. Bis 0.9.18 kam er von der Oberfläche des LoxBerry. Am 05.09.2026 am Gerät gemessen (LoxBerry 4.0.0.15,
system/css/components.css): deren Regel.lb-content selectgibt es erst seit der neuen Oberfläche, und jede eigene Feldregel mit der Kurzformbackground:löscht sie wieder. Darauf soll sich eine Plugin-Oberfläche nicht verlassen (Regeln/04). Sonst ist an dieser Fassung nichts geändert.
log/plugins liegt auf einer Ramdisk (/dev/zram0). Wird sie geleert — beim
Neustart, durch LoxBerrys log_maint, oder von Hand —, ist die Datei fort. Ein
RotatingFileHandler, der sie beim Start einmal geöffnet hat, schreibt
danach bis zum nächsten Neustart in einen gelöschten Inode: keine
Fehlermeldung, keine Datei, kein Hinweis. Auch die Rotation greift dann nicht
mehr.
Diese Fassung benutzt deshalb WachsameRotation in bin/weissware.py — einen
umlaufenden Handler, der vor jeder Zeile Gerätenummer und Inode vergleicht und
nötigenfalls neu öffnet. Die Standardbibliothek hat für den einen Fall den
WatchedFileHandler und für den anderen den RotatingFileHandler, aber
nichts, was beides kann; deshalb die eigene Klasse.
Auf dem LoxBerry geeicht, vier Prüfungen und in beide Richtungen: schreiben, nach dem Löschen weiterschreiben, Umlauf bei Überlänge, nach dem Umlauf erneut löschen. Mit dem alten Handler ist die Zeile nach dem Löschen verloren und bleibt es, mit dem neuen steht sie in der wieder angelegten Datei. Auf einem Windows-Arbeitsplatz lässt sich das nicht messen — dort kann eine offene Datei gar nicht gelöscht werden.
Aufgefallen ist die Bauart am Heimkino-Plugin, dessen Dienst sieben Stunden
ohne Protokolldatei lief, und am laufenden Gerät belegt: der
Midea2Lox-Dienst hielt midea2lox.log (deleted) offen, während unter
demselben Namen längst eine neue Datei fortgeschrieben wurde — von außen sah
das Plugin gesund aus. Elf Linien tragen dieselbe Bauart; alle elf sind am
06.09.2026 nachgezogen worden.
Die zweite Hälfte gehört dem Startskript. bin/dienst.sh hängte die
Ausgabe des Dienstes mit nohup … >> "$LOGDATEI" an dieselbe Datei, die
der Handler führt. Damit hält die Shell einen zweiten, anhängenden Deskriptor
darauf — und der bleibt auf der gelöschten Datei stehen, gleich wie gut das
Programm nachfasst. Am Gerät gemessen (06.09.2026): sieben laufende Dienste
hielten so eine gelöschte Protokolldatei offen. Die Ausgabe geht jetzt in
weissware_start.log, das bei jedem Start geleert wird; das Protokoll gehört
allein dem Handler. Übernommen von AnkerSolix, das es seit 0.9.6 so macht.
Im Sandkasten am Gerät geprüft, in beide Richtungen: mit dem alten Skript steht die Dienstausgabe im Protokoll und es gibt keine Startdatei, mit dem neuen ist es umgekehrt — Start, Startdatei, unberührtes Protokoll und Stopp je sechs von sechs.
Vier Gründe, in der Reihenfolge ihres Gewichts:
- Zwei Takte statt einem. Ein starrer Zyklus ist entweder zu langsam für eine brauchbare Restzeit oder zu schnell für die Ratengrenzen. Dieses Plugin fragt weit ab, solange nichts läuft, und eng, sobald ein Gerät arbeitet — umgeschaltet wird von selbst. Token werden im Hintergrund erneuert; ein Ausfall eines Anbieters lässt die übrigen unberührt.
- Jeder Rohwert. Was der Anbieter liefert, geht als Zahl oder Text weiter — Programm, Fortschritt, Schleuderdrehzahl, Energie, Wasser. Der Knopf Rohdaten als JSON ansehen zeigt zusätzlich die komplette Antwort.
- Jede Miniserver-Generation. Der LoxBerry übernimmt die Arbeit und schickt dem Miniserver einfache HTTP-Antworten oder MQTT-Nachrichten. Ein Gen-1-Miniserver reicht.
- Ein Modell für alle Marken. In der Loxone Config gibt es eine Sorte virtueller Eingänge, egal von wem das Gerät ist.
Samsung hat die Lebensdauer neu ausgestellter Personal Access Tokens am 30.12.2024 auf 24 Stunden verkürzt und die Ratengrenzen für neue Tokens gesenkt. Für Dauerbetrieb verlangt Samsung eine „Service Integration“ — eine registrierte Anwendung mit einem von außen erreichbaren Webhook. Das kann ein LoxBerry hinter einem Router nicht ohne Weiteres.
Praktisch heißt das: Sie müssen das Token täglich erneuern. Das Plugin sagt das in der Oberfläche, im Reiter Test und in jeder Fehlermeldung, statt es zu verschweigen. Wer ein Token von vor dem 30.12.2024 hat, ist nicht betroffen. Wer damit nicht leben will, lässt SmartThings aus — Home Connect und Miele laufen unabhängig davon.
Das Plugin spricht alle drei Schnittstellen selbst an und braucht dafür nur
requests. Grund: die verbreiteten fertigen Pakete verlangen ein Python, das
es auf keinem LoxBerry gibt.
| Paket | verlangt | Debian 12 (3.11) | Debian 13 (3.13) |
|---|---|---|---|
aiohomeconnect ab 0.31 |
Python 3.13 | nein | ja |
pymiele ab 0.6.2 |
Python 3.14 | nein | nein |
pysmartthings ab 4.0 |
Python 3.13 | nein | ja |
| dieses Plugin | Python 3.9 | ja | ja |
Kein Anbieter startet ein Programm aus der Ferne, solange nicht am Gerät
selbst die Fernstart-Freigabe gegeben wurde (Home Connect: Taste
Fernstart; Miele: MobileStart). Sie erlischt meist nach dem Programm. Das
Plugin führt sie als Wert FERNSTART und weist einen Start ohne Freigabe von
sich aus ab, statt in eine 409-Antwort des Anbieters zu laufen, deren Grund
in der Fehlermeldung untergeht.
Der gedachte Ablauf: Wäsche einfüllen, Programm wählen, Fernstart drücken — und Loxone drückt auf Start, wenn die Sonne scheint.
Die Ansage („Gerät ist fertig“) kennt neben Loxone Music Server, MusicServer4Home/Audioserver4Home und einer eigenen Vorlage zwei Ausgabearten über andere Plugins auf demselben LoxBerry, beide ab Werk nicht gewählt:
| Ausgabeart | Plugin | Adresse (nur 127.0.0.1) |
|---|---|---|
| Alexa-NG (Amazon Echo) | Alexa-NG | /plugins/alexang/index.php |
| Google-Lautsprecher | Chromecast 4 Lox NG ab 1.3.15 | /plugins/chromecast-4lox-ng/index.php |
Eingerichtet wird zuerst im anderen Plugin (Anmeldung bzw. „Sprachausgabe für
andere Plugins“ und ein Sprechtoken), danach hier: Gerät (leer =
Standardgerät), Lautstärke (leer = Ansagelautstärke) und dasselbe Sprechtoken.
Jede Ausgabeart hat ihr eigenes Token. Das Token ist ein Geheimnis wie ein
Kennwort: es geht nur im POST-Körper hinaus, steht nie in der Seite, nie in
der Sicherung (eine Sicherung mit Token wird abgewiesen, beim Zurückspielen
bleibt das geltende) und nie im Protokoll; vom Ansagetext steht dort nur die
Länge. Als gesendet gilt nur HTTP 200 mit SPRECHEN;OK=1. Fällt das andere
Plugin aus, entfällt die Ansage – es wird nicht auf einen anderen Lautsprecher
ausgewichen. Die Testansage zeigt die Antwortzeile, der Reiter Test prüft das
Token ohne Ansage (nur bei geöffnetem Reiter).
bin/weissware.py Abrufdienst (Python, eigene venv)
bin/dienst.sh Start, Stopp, Wächter
cron/cron.01min minütlicher Wächter
webfrontend/htmlauth/ Bedienoberfläche (fünf Reiter)
webfrontend/html/ Endpunkt für den Miniserver + gemeinsame Bibliothek
Drei Aufgaben, drei Dateien. Weder Oberfläche noch Endpunkt sprechen je selbst mit einem Anbieter — sie lesen den Zwischenspeicher und legen Befehle in einer Warteschlange ab.
Alle Aufrufe brauchen das Token aus dem Reiter Einbindung in Loxone. Statt der laufenden Nummer darf überall die Kennung des Anbieters stehen.
| Aufruf | Zweck |
|---|---|
?token=T&aktion=status&geraet=N |
WEISSWARE;OK=..;ZUSTAND=..;LAEUFT=..;FERTIG=..;VERBUNDEN=..;TUER=..;FORTSCHR=..;RESTMIN=..;STARTMIN=..;LAUFMIN=..;FERNSTART=..;FERNBED=..;NETZ=..;ALTER=..;FERTIGUM=.. plus eine Textzeile |
?token=T&aktion=verbrauch&geraet=N |
VERBRAUCH;OK=..;ENERGIE=..;WASSER=..;TEMP=..;SCHLEUDER=..;ALTER=.. |
?token=T&aktion=geraete |
Liste aller erkannten Geräte |
?token=T&aktion=roh |
vollständiges Abbild als JSON |
?token=T&aktion=start&geraet=N |
am Gerät gewähltes Programm starten |
?token=T&aktion=start&geraet=N&programm=… |
bestimmtes Programm starten — nur Home Connect |
?token=T&aktion=stop&geraet=N / pause / fortsetzen |
Programm abbrechen, anhalten, fortsetzen |
?token=T&aktion=ein&geraet=N / aus |
Gerät ein- und ausschalten |
?token=T&aktion=abruf |
sofort abrufen statt auf den Takt zu warten |
ZUSTAND ist eine Stufe: 0 aus, 1 bereit, 2 läuft, 3 pausiert,
4 fertig, 5 Störung.
Ein Strich als Wert heißt: dieser Wert liegt nicht vor. Es wird bewusst
keine 0 gesendet. Bei Miele ist das besonders wichtig — dort heißt -32768
ausdrücklich „gerade kein Wert“, und 0 Minuten Restzeit hieße „fertig“.
Was OK=1 nicht heißt. Der Anbieter hat die Anfrage angenommen. Ob das
Gerät anläuft, zeigt erst der nächste Abruf; wer sicher sein will, wertet
LAEUFT aus.
Schaltende Befehle brauchen geraet=. Jeder schaltende Aufruf außer
abruf nennt das Gerät; ohne antwortet das Plugin mit HTTP 400 und
SET;OK=0;GRUND=GERAET_FEHLT und schaltet nichts. ein und aus sind ein
Sollwert: derselbe Wert an dasselbe Gerät binnen 60 Sekunden geht nicht noch
einmal hinaus, die Antwort trägt dann UNVERAENDERT=1 (vor MELDUNG).
Start, Stopp und Pause sind Aufträge und werden nicht gebremst.
Wann OK=0 ist. OK geht auf 0, sobald der letzte erfolgreiche Abruf
mehr als dreimal den Ruhetakt zurückliegt (ALTER steht unverändert daneben)
oder sein Zeitpunkt mehr als 5 Sekunden in der Zukunft liegt. Schweigt bei
mehreren Anbietern einer, bleiben seine Geräte mit den zuletzt gemessenen
Werten stehen und antworten mit OK=0; über MQTT geht je Gerät
geraetN/ok (0/1) und geraetN/ts hinaus, beide nie zurückbehalten.
Antworten ohne Werte. Gab es noch nie einen gemessenen Stand, antworten
status und verbrauch mit HTTP 503 und GRUND=KEINE_DATEN. Ein Gerät, das
ein fehlerfrei antwortender Anbieter nicht mehr führt, gilt als entfernt:
GRUND=GERAET_ENTFERNT, und seine zurückbehaltenen MQTT-Themen bekommen
einmal -. Kommt es zurück, bekommt es seine alte Nummer.
Getrenntes Gerät. Führt der Anbieter ein Gerät als getrennt (VERBUNDEN=0, etwa
eine Home-Connect-Waschmaschine ohne WLAN), bleiben am Endpunkt wie über MQTT die
zuletzt gemessenen Zustände stehen; VERBUNDEN=0 sagt, dass sie alt sind. Ein Strich
steht nur, wenn es seit dem ersten Abruf nie einen Wert gab.
| Wert | Home Connect | Miele | SmartThings |
|---|---|---|---|
| Fortschritt in Prozent | ja | gerechnet aus Rest- und Laufzeit | nein |
| Restzeit | ja (Sekunden, wird umgerechnet) | ja (Stunden/Minuten) | aus dem Fertigzeitpunkt |
| Energie und Wasser | nein | nur mit EcoFeedback | nur Energie |
| Fernstart-Freigabe | ja | ja (mobileStart) |
ja |
Was fehlt, ist ein Strich — keine 0.
Zugangsdaten und Anmeldemarken liegen in
config/plugins/weissware/zugang.json und
data/plugins/weissware/token.json, beide mit den Rechten 0600, und nie in der
Loxone-Projektdatei. Verbindungen gibt es nur zu den eingeschalteten Anbietern,
bei der Installation zu PyPI und — wenn die Ansage eingeschaltet ist — zu dem
Audio-Server, dessen Adresse Sie im Reiter Einstellungen eintragen, bzw. zu
Alexa-NG oder Chromecast 4 Lox NG auf diesem LoxBerry (127.0.0.1).
zugang.json hält die Client-Geheimnisse von Home Connect und Miele sowie das
SmartThings-Token. Geschrieben wurde sie mit file_put_contents, danach
kam chmod 0600. Zwei Dinge daran:
- Zwischen Anlegen und
chmodsteht die Datei mit den Rechten aus der umask da — mit den Geheimnissen bereits darin. Jetzt werden die Rechte an der Nebendatei gesetzt, bevor sie an ihren Platz umbenannt wird. - Der Dienst liest dieselbe Datei.
file_put_contentskürzt sie zuerst auf null; wer in diesem Augenblick liest, sieht keine Zugangsdaten und meldet sich vergeblich an.rename()ist unteilbar.
ww_paths() fiel auf den festen Namen weissware zurück, sobald
config/plugins/<ordner> noch fehlte. Hängt LoxBerry bei einer
Zweitinstallation einen Zähler an (weissware_01), zeigten deren Pfade damit
auf die erste Installation — gemeinsame zugang.json mit den Geheimnissen
dreier Anbieter, gemeinsame Warteschlange, gemeinsames Protokoll. Maßgeblich
ist jetzt LBPPLUGINDIR.
ww_befehl_senden() schrieb json_encode($befehl) direkt weiter. Gibt
json_encode bei ungültigem UTF-8 false zurück, macht file_put_contents
daraus eine leere Zeichenkette, schreibt null Byte und meldet Erfolg — der
Rückgabewert ist 0, nicht false, die Prüfung auf === false greift also
nicht. ww_config_speichern() im selben Modul macht es seit jeher richtig.
Dazu: Der User-Agent an die drei Herstellerclouds trug noch 0.9.0.
Vierzehn Punkte aus einer Durchsicht. Zehn trafen zu, drei teilweise, einer nicht. Bemerkenswert an dieser Liste: die Hälfte sind Altlasten aus fremden Plugins, aus denen Bausteine übernommen wurden. Die waren alle real.
| Fundstelle | war | ist |
|---|---|---|
ww_vorlage() |
'AUDI_' . $nummer . '_' . $feld |
'WW_' … |
ww_paths(), Ersatzzweig |
config/vw.backup.json |
config/weissware.backup.json |
vor ww_zugang_speichern() |
zwei PHPDoc-Blöcke übereinander | einer |
vor ww_verbrauch_felder() |
„Die Werte des Lade-Endpunkts“ und „…des Verbrauchs-Endpunkts“ | nur der richtige |
nach ww_verbrauch_felder() |
„Die Werte des Wartungs-Endpunkts“ ohne Code dahinter | entfernt |
htmlauth/index.php |
ein \1 vor <h2> — Rest einer Suchen-und-Ersetzen-Aktion |
entfernt |
Das AUDI_ war der folgenreichste: in Loxone Config wären die Bausteine
unter fremdem Namen gelandet. Das \1 stand sichtbar in der Oberfläche.
Trifft zu, und die Beschreibung war genau: ohne Ausfälle erschien „keine Ausfälle“ zweimal, mit Ausfällen wurden sie erst einzeln je Anbieter und danach noch einmal gesammelt aufgeführt. Der zweite Block ist weg — die Einzelaufstellung bleibt, sie nennt den Grund.
Trifft zu. json_schreiben() legte die Nebendatei mit tmp.open("w") an und
setzte die Rechte erst danach. Gemessen mit umask 022:
| während des Schreibens | danach | |
|---|---|---|
| bisher | 0644 | 0600 |
jetzt (os.open mit mode) |
0600 | 0600 |
In token.json steht ein gültiger Zugang zu drei Herstellerclouds.
Antwortdateien blieben nach dem Lesen liegen — unlink ergänzt.
Wartezeit aus dem Webfrontend war auf 30 s gedeckelt; jetzt 12. Ein Webserver bricht vorher mit 504 ab, und der Dienst arbeitet den Befehl ohnehin zu Ende — die Warteschlange liegt im Dateisystem, nicht in der Anfrage.
Nicht löschbare Befehlsdatei. Der Fehler wurde verschluckt und der Befehl trotzdem ausgeführt; die Datei blieb liegen und wurde in jedem weiteren Durchgang erneut abgearbeitet. Bei einem Abruf wäre das nur Last — bei „Waschmaschine starten“ läuft das Gerät jede Runde neu an. Jetzt wird der Befehl in diesem Fall nicht ausgeführt und der Grund gemeldet.
Alte PID-Datei blieb liegen, wenn der Prozess fort war. Sie wird jetzt entfernt, sobald sie sich als Leiche erweist.
Cron über /bin/bash. Umgesetzt: geht das Ausführungsrecht verloren,
schlüge der unmittelbare Aufruf lautlos fehl — die Ausgabe geht nach
/dev/null.
hc_anmeldung.json beim Verwerfen. Umgesetzt. Blieb die Datei liegen,
versuchte der nächste Anmeldeversuch die alte, längst abgelaufene Sitzung
abzuschließen — genau das soll der Knopf verhindern.
Protokoll ganz eingelesen. Der Speicherhinweis war berechtigt, tail ist
aber der langsamste der drei Wege (rund 1,9 ms gegen 0,05 ms beim
Rückwärtslesen mit fseek). Umgestellt auf fseek.
Fehlende Timeouts bei requests. Jeder einzelne Aufruf in
weissware.py trägt timeout=30 — 17 Stellen, nachgezählt. Richtig
an dem Punkt ist nur die Folge: 30 s sind lang genug, dass dienst.sh stop
in den harten kill -9 nach zehn Sekunden laufen kann. Das ist aber eine
Abwägung zwischen „Abruf abbrechen“ und „sauber beenden“, kein fehlender
Timeout.
Die Prozessprüfung war zu weich. grep -qa "weissware.py" über die ganze
Befehlszeile: hat eine wiederverwendete Prozessnummer einen Editor mit
geöffneter weissware.py erwischt, galt der als laufender Dienst. Geprüft
werden jetzt zwei Dinge argumentweise — argv[1] ist genau das Skript, argv[0]
ist ein Python. Nur das erste zu prüfen reicht nicht: nano <pfad>/weissware.py
führt den Pfad ebenfalls als zweites Argument.
Die fünf Reiter brauchten JavaScript. sm-active wurde ausschließlich
vom Skript vergeben — ohne JavaScript war keine Fläche sichtbar. Reihenfolge,
Positivliste und Beschriftung kommen jetzt aus einem Feld, der Server setzt
die Klasse selbst. Die id der Bereiche im Rumpf ist die zweite Stelle —
sie lässt sich nicht miterzeugen, deshalb misst der Reiter Test die
Übereinstimmung aller drei Stellen nach.
Es gab kein Uninstall-Skript. Die Sicherungen mit den Zugangsdaten von bis
zu drei Herstellerclouds liegen neben dem Konfigordner — gelöscht wird beim
Deinstallieren nur das Verzeichnis. Sie wären für immer auf der Karte
geblieben. uninstall/uninstall gibt es jetzt; es hält den Dienst an,
überschreibt Sicherungen, Anmeldemarken und die halbe Anmeldung und entfernt
sie.
- Die Baustein-Liste widersprach sich. Typ- und Eingangsspalte stammten wörtlich aus AudiConnect, Namen und Parameter waren für Weissware neu geschrieben — um eine Zeile versetzt. Zehn von 34 Zeilen trugen dadurch einen Typ, der nicht zu ihrem Parameter passte (ein virtueller Ausgang mit Schwellwerten, eine Benachrichtigung mit „Ein > 0,5“). Die Kette ist neu gelegt: streng zweieingängige UND/ODER, keine Vorwärtsverweise. Alle 22 Namen sind die bisherigen.
htmlauth/index.phpgab$ww_fz['modell']aus — das Feld gibt es nicht. Unter PHP 8 stand die Warnung in der Tabellenzelle.- Die Prüfzeile „Wie frisch ist das Abbild?“ rechnete mit
$cfg['intervall'], einem Schlüssel, den es nirgends gibt. Die Schwelle lag damit unabhängig vom eingestellten Takt immer bei 600 s — ein rotes Kreuz, das nichts bedeutet. - Ein Programmschlüssel wurde bei Miele und SmartThings verworfen und der
Befehl trotzdem mit
OK=1quittiert. Er wird jetzt abgewiesen; er wirkt nur bei Home Connect.
Dazu: SOC= in der Gegenprobe-Tabelle, ST_BEFEHL fest auf
washerOperatingState (SmartThings-Geschirrspüler wurden gelesen, aber nicht
geschaltet), die stillschweigend auf 12 s gedeckelte Wartezeit, die Reiterleiste
als Schleife — und „aus der Audi-Schnittstelle“ im Reiter Einstellungen.
- Feste Gerätenummern. Die Nummer entstand aus der Position in einer
sortierten Liste. Fiel ein Anbieter aus, rückten die übrigen Geräte auf, und
virtueller Eingang, MQTT-Thema und Ausgangsadresse zeigten still auf ein
anderes Gerät. Die Zuordnung steht jetzt in
data/plugins/weissware/geraetenummern.json; der erste Lauf nummeriert genau so, wie bisher gezählt wurde, damit bestehende Anlagen ihre Adressen behalten. - Nachfass-Abruf nach jedem angenommenen Schaltbefehl statt Warten auf den Takt.
FERTIGUM— der voraussichtliche Fertigzeitpunkt wurde gerechnet und nirgends ausgeliefert.ts,fehler_folgeund Ausfälle über MQTT, auch bei einer Störung. Über MQTT gibt es kein Alter; die Gegenseite rechnet es aus dem Zeitstempel.- Trockenlauf — zeigt, welche Sperre greift und welche Anfrage hinausginge, und sendet nichts. Er läuft durch denselben Code wie ein echter Befehl.
- Mitschnitt des Datenverkehrs: ab Werk aus, feste Frist, 500 kB Grenze, Zugangsdaten und Token werden vor dem Schreiben entfernt.
- Vorlagen für Status, Verbrauch, virtuelle Ausgänge und den MQTT-Weg,
je erkanntem Gerät ein Knopf. Angelegt wird nur, was das Gerät beim letzten
Abruf geliefert hat — sonst trägt Loxone
DefVal="0"ein, und eine 0 sieht aus wie ein Messwert. - Ansage auch bei Störung und erloschener Fernstart-Freigabe, mit Ruhezeit. Beendete Programmläufe mit Verbrauch werden festgehalten.
- Der Wächter misst das Erzeugnis statt der Prozessnummer, der Reiter Test ruft den eigenen Endpunkt wirklich auf und stellt 25 Fragen in 35 Prüfzeilen statt 15.
?aktion=dienst ist neu: DIENST;OK=..;GERAETE=..;LAEUFT=..;FERTIG=..;FEHLERFOLGE=..;AUSFAELLE=..;ALTER=..
Eine Durchsicht der veröffentlichten 0.9.17. Fünf schwere Befunde, alle gemessen, alle gegen PHP 7.4.33 und 8.4.24.
Die eigene Sicherung war nicht zurückspielbar. Die Oberfläche schreibt
bei jedem Speichern den Schlüssel tts in die Konfiguration; ww_vorgaben()
kannte ihn nicht, und ww_sicherung_lesen() prüft jeden Schlüssel gegen genau
diese Liste. Ergebnis: der Knopf Einstellungen sichern erzeugte eine Datei,
die der Knopf Einstellungen zurückspielen mit „Unbekannte Einstellung: tts“
grundsätzlich verweigerte — der Umzug auf einen zweiten LoxBerry, der erklärte
Zweck der beiden Knöpfe, war nie möglich.
Der unangemeldete Endpunkt legte eine Datei an, bevor er das Token prüfte.
webfrontend/html/index.php rief ww_config(), und die Funktion heilte eine
fehlende Konfiguration aus der Zweitschrift. Gemessen: ein Aufruf ohne Token
wurde richtig mit GRUND=TOKEN abgewiesen — und legte weissware.json
trotzdem an. Wer die Adresse kannte, schaltete damit eine alte Sicherung
wieder scharf, samt steuerung_ein und altem Aktionstoken. ww_config() hat
jetzt einen Schalter, der Endpunkt ruft ww_config(false).
Eine Sicherung ohne Aktionstoken leerte das Token — und die Zweitschrift.
Die Lesefunktion begann mit ww_vorgaben(); alles, was in der Datei fehlte,
kam aus den Werkseinstellungen. Der Bediener las „17 Werte übernommen“ und
hatte danach kein Token mehr: der Endpunkt antwortet KEIN_TOKEN_GESETZT,
jeder virtuelle Eingang bekommt 403 und wertet ihn nicht aus, und beim
nächsten Öffnen der Oberfläche wird ein neues gewürfelt. Grundlage ist jetzt
der Bestand, ein fehlendes Token ist eine Beanstandung, und die
Zweitschrift wird nur mitgezogen, wenn der Stand ein Token trägt.
Werte aus der Sicherungsdatei wurden nie geprüft. Nur die Schlüsselnamen.
Eine Datei mit takt_ruhe als Objekt und aktionstoken als Feld ging ohne
eine einzige Beanstandung durch; aus dem Feld wurde im Endpunkt die
Zeichenkette Array — ein Token, das jeder kennt. Jeder Wert läuft jetzt
durch dieselben Muster und Grenzen wie das Formular.
Vier Dateien überlebten kein Update. preupgrade.sh sicherte nur
weissware.json und zugang.json. Der Installer räumt beim Upgrade aber auch
data/plugins/<ordner>/ ab, und dort liegen token.json (die Anmeldung an
allen drei Herstellerclouds), geraetenummern.json (die Adressen in Loxone),
laeufe.json und der Merker soll_laufen. Ohne den letzten startete auch der
Wächter nicht mehr: das Plugin schaltete sich bei jedem Auto-Update still
selbst ab. Alle vier werden jetzt gesichert und zurückgelegt, und der Dienst
läuft danach wieder an, wenn er vorher lief.
Dazu:
- Die Sicherungsdatei trägt jetzt die Zugangsdaten — der Warntext am Knopf behauptete das schon, sie enthielt sie aber nicht. Die Anmeldemarken der Anbieter bleiben draußen; nach einem Umzug ist einmal neu anzumelden.
- Nach dem Zurückspielen wird der Dienst nachgezogen, und es steht dabei, was mit ihm geschah. Die beiden Handler stehen jetzt vor dem Ladeblock — vorher zeigte die Seite nach einem Zurückspielen jedes Feld im Vorzustand und im Reiter Einbindung in Loxone das alte Token.
- Zugangsdaten werden erst geschrieben, wenn das Formular fehlerfrei ist. Wer den Takt vertippte und gleichzeitig ein Client-Geheimnis austauschte, las eine Fehlermeldung — und hatte das Geheimnis doch schon ausgetauscht.
weissware.jsonbekommt 0600 und wird unteilbar geschrieben. Sie trägt das Aktionstoken, mit dem der Endpunkt schaltende Befehle annimmt; bisher bekam nurzugang.jsondiese Rechte.ww_dienst_schalter(),ww_trockenlauf()undww_dienst()melden den Fehlerfall nicht mehr als Erfolg. Fehlte die virtuelle Umgebung, meldete die Oberfläche „Home Connect ist angemeldet“, während gar nichts lief; istexecgesperrt, blieb der vorbelegte Rückgabewert 0 stehen.- Die Erläuterung zur Baustein-Liste verwies auf die alte Nummerierung: sechs von sieben Verweisen zeigten auf den falschen Baustein (die UND-Verknüpfung ist #23, nicht #14; die Einschaltverzögerung #18, nicht #15; die Benachrichtigung #28, nicht #21). Wer die Liste abarbeitete, verdrahtete falsch.
- Die Prüfzeile Vorlage und Statuszeile suchte den Endpunkt über
dirname(__DIR__). Installiert liegen die Bäume getrennt — sie konnte auf keiner Anlage je etwas messen. Jetzt mit Kandidatenliste. gewaehlt_textist ein Textthema und bekommt keinen analogen Eingang mehr; er stand in Loxone dauerhaft auf 0.- 19 ASCII-Umschriften in angezeigtem deutschem Text (
waehrend,Oberflaeche,heisst, …) und Reste eines Fahrzeug-Plugins (carconnectivity, „mit Fahrzeug prüfen“) sind fort. DerUser-Agentkommt aus einer Fassungskonstante statt fest aus0.9.1. - Die englische Hilfe nennt beim MQTT-Abo jetzt wie die deutsche den Unterschied zwischen Gateway V1 und V2.
Nicht behoben, weil ohne Konto und Gerät nicht entscheidbar: ob die Zuordnung der Miele-Statuscodes, die SmartThings-Energieeinheit und der Home-Connect-Temperatur-Enum stimmen. Das steht weiter unter ungeprüft.
Die Protokollkappung (512 000 Byte) stand in
webfrontend/html/ww_lib.php:1456. PHP merkt sich aber die Antworten von
stat(): innerhalb eines Prozesses sieht filesize() die erste Größe
und danach nie wieder eine neue — file_put_contents(…, FILE_APPEND) macht
den Eintrag nicht ungültig. Die Kappung fällt dann still aus.
Gemessen am 29.08.2026, 20 000 Zeilen im selben Prozess:
ohne clearstatcache |
mit | |
|---|---|---|
| PHP 7.4.33 | 1 220 000 Byte, nicht gekappt | 220 332 Byte, gekappt |
| PHP 8.4.24 | 220 332 Byte, gekappt | 220 332 Byte, gekappt |
Die beiden PHP-Fassungen verhalten sich also verschieden — und LoxBerry 3.x fährt 7.4. Wer nur unter 8.4 misst, sieht den Fehler nie. Folgen hatte das hier nicht: die Aufrufer sind kurzlebig, und ein frischer Prozess kappt richtig. Eine Funktion darf aber nicht davon abhängen, wer sie wie oft ruft.
Abhilfe: clearstatcache(true, …) vor dem Tor; der zweite Parameter
beschränkt das Leeren auf diese eine Datei. Dasselbe Muster tragen Robonect,
Saugroboter, SignalBot, Octopus, Sprachsteuerung und WärmepumpeCloud schon
länger — es ist am 29.08.2026 im ganzen Bestand nachgezogen worden.
MIT — siehe LICENSE.
Home Connect, Bosch, Siemens, Neff, Gaggenau und Constructa sind Marken der BSH Hausgeräte GmbH, Miele und Miele@home Marken der Miele & Cie. KG, SmartThings und Samsung Marken der Samsung Electronics Co., Ltd. Dieses Projekt steht in keiner Verbindung zu diesen Unternehmen und wird von ihnen weder herausgegeben noch unterstützt; es benutzt lediglich deren öffentlich angebotene Schnittstellen. Alle drei können sie ohne Ankündigung ändern, womit dieses Plugin ganz oder teilweise unbrauchbar würde.