uhrzeit_erfuellt() verglich beim Datentyp "date" bisher das volle Datum
samt Jahr. Damit musste jede Automatik mit einer Datumsbedingung jedes Jahr
von Hand nachgezogen werden - die Frostwarnung stand deshalb fest auf
2026/2027 und waere zum naechsten Jahreswechsel wieder falsch gewesen.
Verglichen wird jetzt nur noch (Monat, Tag). Ein Fenster, das ueber den
Jahreswechsel reicht, laesst sich damit weiterhin nicht in einer einzigen
Bedingung ausdruecken - dafuer braucht es zwei Bedingungen ohne obere bzw.
untere Grenze in getrennten (oder verbundenen) Gruppen, wie es die
Frostwarnung schon vorher tat.
Dabei die eigentliche Ursache gefunden, warum die Frostwarnung nie
ausloeste: alle sechs Bedingungen standen in derselben Gruppe (group_no = 0)
und damit UND-verknuepft - ein Datum kann nicht gleichzeitig im
September/November- UND im Februar/April-Fenster liegen. Die zweite Haelfte
(Bedingungen 271-273) steht jetzt in einer eigenen Gruppe, ODER-verknuepft
mit der ersten. Reine Datenaenderung, kein Schema-Update noetig.
Getestet gegen das echte Regelwerk, ohne Schaltbefehle: vorher an keinem
Tag erfuellbar, nachher am 22.09. (3,6 Grad) korrekt ausgeloest - siehe
automation_log, push_abos.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Der Funk von der Box zum Motor ist unbestaetigt. Die Box nimmt einen Befehl
an und meldet Erfolg, auch wenn er den Motor nie erreicht - dann faehrt der
Behang gar nicht. Erkennbar ist das allein daran, dass er hinterher nicht
dort steht, wo er stehen soll. Genau das ist jetzt die Bedingung fuer den
zweiten Versuch.
Der Ansatz stand schon in der Datei, konnte aber nicht wirken:
Die Endstellung wurde gegen Neigung 0 geprueft, obwohl gerade die
gewuenschte Neigung geschickt worden war. Damit lieferte die Wartefunktion
immer "nicht erreicht" - verlorener Befehl und geglueckte Fahrt sahen gleich
aus, jedes Kommando lief zweimal 120 Sekunden und ging zweimal raus.
aktion["actor_name"] gibt es im Auftrag nicht; der Name steht eine Ebene
hoeher. Der KeyError flog nach der Vorstufe, also nachdem "Neigung 0" schon
draussen war: der Behang fuhr auf Position und liess die Lamellen offen. 23
Mal im Protokoll, zuletzt am 21.09. um 18:49 an acht Jalousien.
Jetzt:
_positionsZiel() leitet das Ziel auch fuer "up" und "down" ab (0 bzw. 100
Prozent Schliessung). Ohne das haette ausgerechnet der haeufigste Befehl
kein pruefbares Ziel und der Wiederholversuch liefe fuer ihn leer.
_zieleErreicht() vergleicht mit zwei Prozentpunkten Spielraum: io-Motoren
melden fuer befohlene 100 gern 99 oder 101. Ohne Toleranz gaelte eine
geglueckte Fahrt als verloren. None heisst "ist egal" - damit funktionieren
auch Rollladen ohne Lamellen und reine Neigungsbefehle.
_fahren() haelt die Schleife an einer Stelle. Vor der Wiederholung wird
noch einmal nachgesehen, weil der Stand nur alle zwei Sekunden gelesen wird
und der Behang in der letzten Sekunde angekommen sein kann. Ohne pruefbares
Ziel (stop, my, wink) wird einmal geschickt und nicht gewartet.
Wiederholt wird ausdruecklich immer, wenn das Ziel nicht erreicht ist - auch
wenn der Behang unterwegs war und woanders stehengeblieben ist. Die
Unterscheidung waere ueber core:MovingState moeglich und ist bewusst nicht
gewollt.
Geprueft ohne Schaltbefehle: Ziel erreicht -> einmal gesendet; Befehl
verloren -> zweimal; erster verloren, zweiter kommt an -> zweimal, Erfolg;
99 statt 100 -> einmal; Rollladen ohne Lamellen, stop und reine Neigung
jeweils richtig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher loeste eine Automatik in dem Moment aus, in dem die Bedingung wahr
wurde. Fuer Dauerzustaende taugt das nicht: "der Wasserzaehler laeuft" ist
jedes Haendewaschen, gemeint war "laeuft seit einer halben Stunde ohne
Pause".
hold_secs schiebt die Flanke nach hinten, statt sie zu verbrauchen -
cond_met bleibt bis dahin 0, und alles danach (Tagessperre, Sperrzeit,
Protokoll) bleibt unberuehrt. Ein Aussetzer setzt die Zeit zurueck, das
Zugehen des Zeitfensters ebenfalls.
Gezaehlt wird im Speicher (erfuellt_seit) wie war_aktiv: nach einem Neustart
weiss niemand, ob die Bedingung zwischendurch anlag.
hold_secs = 0 ist die Vorgabe und heisst: wie bisher.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neuer BenachrichtigungTransport hinter der Aktor-URL "Benachrichtigung":
push schickt an alle angemeldeten Geraete (homeMesh.push_abos), mail ueber
den SMTP-Zugang aus der config.ini. Ein Abo, das der Push-Dienst mit 404
oder 410 ablehnt, wird geloescht statt weiter angeschrieben.
Daneben hoert der Runner auf benachrichtigung/# - darueber schickt die
Probe aus den Einstellungen, ohne dass die Weboberflaeche verschluesseln
muesste. Zugestellt wird im Haupttakt, nicht im MQTT-Faden.
pywebpush, py_vapid und http_ece liegen wie die uebrigen Fremdpakete im
Ordner, nicht in site-packages; transports.py haengt ihn an den Suchpfad.
Das Schluesselpaar erzeugt vapid_erzeugen.py und bleibt ausserhalb des Git.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- autoActions/README: Rahmen gilt heute oder am Vorabend fuer morgen,
vorabend.sql beim Einrichten, startSolarServer.sh startet drei Prozesse,
Beispiel der Verkettung wie die echten Automatiken, Neustart ueber SSH
- README: skoda_ladepunkte und skoda.conf, Werkzeuge-Tabelle, Datenbank
alarm ist geloescht
- config.ini.example: [alarm] und zeit.py entfernt, die gibt es nicht mehr
- Runner-Kopfkommentar verweist nicht mehr auf auto_watering.py
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mit next_day = 1 gelten Wochentage, Ferien und Feiertage fuer morgen
(gemeinter_tag). kalender() merkt sich dafuer heute und morgen und haelt
einen fehlgeschlagenen Abruf nicht mehr den ganzen Tag fest. Uhrzeit,
Zeitfenster und once_per_day bleiben beim heutigen Tag. vorabend.sql legt
die Spalte an.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"An Wochenenden und Feiertagen" liess sich bisher gar nicht schreiben.
Die Wochentagsmaske kennt nur Samstag und Sonntag, und ein Feiertag am
Dienstag ist fuer sie eben ein Dienstag; on_holiday war ja/nein und
konnte nur wegnehmen, nie hinzufuegen.
on_vacation und on_holiday tragen deshalb jetzt drei Werte: 0 nie,
1 egal (Vorgabe), 2 zusaetzlich. Der dritte zaehlt wie ein angehakter
Wochentag - Sa+So angehakt und "Feiertage: zusaetzlich" ergibt den
gewuenschten Fall, und mit gar keinem angehakten Tag sogar "nur an
Feiertagen".
In tag_passt() wird erst geoeffnet, dann gesperrt: ein Verbot schlaegt
eine Erweiterung. Wer in den Ferien nie laeuft und an Feiertagen
zusaetzlich, laeuft an einem Feiertag in den Ferien nicht - andersherum
liesse sich "nie" nicht mehr verlassen.
Dazu once_per_day: nach dem Ausloesen bis Mitternacht Ruhe. Fuer alles,
was man hinterher von Hand wieder anders stellt - ein Rollladen, den man
um acht zugezogen hat, soll nicht um neun von selbst wieder auffahren,
weil eine Wolke weiterzieht und die Helligkeitsschwelle ein zweites Mal
steigt. Die Sperrzeit taugte dafuer nicht: sie zaehlt Sekunden und
muesste auf 86400 stehen, womit eine Automatik, die heute um 07:00:05
lief, morgen um 07:00:00 noch gesperrt waere und einen ganzen Tag
ausfiele. Gefragt wird deshalb nach dem Datum von last_run - dieselbe
Funktion, die auch force_once benutzt, nur mit umgekehrtem Vorzeichen.
Die Spalten bleiben TINYINT und tragen die 2 ohne Weiteres; die
vorhandenen Zeilen stehen auf 0 oder 1 und behalten damit genau ihre
bisherige Bedeutung. Umzurechnen gibt es nichts. rahmen_erweitern.sql
setzt nur Kommentare und legt once_per_day an.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anlass: "zehn Minuten nach dem Wecker den Rollladen hoch, aber nur wenn
es dann schon hell ist". Als Verzoegerung an der Aktion war das nicht zu
haben - die Zusatzbedingung gilt erst zum spaeteren Zeitpunkt, und eine
verzoegerte Aktion, die selbst noch Bedingungen prueft, braeuchte ein
zweites Bedingungssystem neben dem ersten. Der zweite Schritt ist also
eine eigene Automatik; was ihr fehlte, war nur ein Bezug auf die erste.
Den gibt jetzt das gerechnete Geraet "Automatiken", Gegenstueck zum
vorhandenen "Zeitpunkt": jede Automatik ist dort ein Messwert, ihr Wert
der Zeitpunkt der letzten Ausloesung. Der Editor braucht dafuer keine
Zeile - er listet Geraete und deren Messwerte.
Der neue Datentyp `elapsed` verhaelt sich dazu wie `deltatime` zum
Sonnenaufgang: "+ 00:10", "ab + 00:10", "vor + 00:10". Ein Minus gibt es
nicht. Gerechnet wird mit dem echten Abstand statt mit der Uhrzeit
innerhalb des Tages - sonst machte ein Lauf von vorgestern die Bedingung
heute wahr. Die offene Form endet trotzdem am Tagesrand, genau wie
"ab 16:30".
Ausgewertet wird topologisch, Ausloeser vor Nachfolger; nur so wirkt ein
Versatz von null noch im selben Takt. Eine pausierte Automatik haelt ihre
Nachfolger mit an: geladen werden nur die aktiven, und der Transport
liefert fuer alle uebrigen einen leeren Wert.
Die Messwerte pflegt ausloeser_nachfuehren() - fuer JEDE Automatik, auch
fuer pausierte. Sonst loeschte ein Pausieren ueber fk_cond_state
ON DELETE CASCADE die Bedingung des Nachfolgers, still. Geloescht wird
nur, was keine Bedingung mehr benutzt.
automatik_ausloeser.sql legt Datentyp und Geraet an.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Discovery-Modul adressiert die neueren Shellys neu: ihre Messwerte
stehen nicht mehr als Feldname zu einer HTTP-Adresse in actor_states,
sondern als Topic mit dem Feld in value_path. Geschaltet werden sie weiter
ueber HTTP - das Geraet bleibt also ein http://-Aktor, waehrend seine Werte
ueber den Broker kommen.
Der Runner teilte Messwerte bisher allein nach der Geraete-URL zu. Die
umgestellten Werte waeren damit beim HTTP-Transport gelandet, der in der
JSON-Antwort nach einem Feld namens "Power_EG/status/em:0" gesucht und nie
gefunden haette: die Werte waeren still auf ihrem letzten Stand
eingefroren. Zugeteilt wird deshalb je Messwert - was wie ein Topic
aussieht (ist_topic()), liest der MQTT-Transport, alles andere geht den
alten Weg. Fuer die aelteren Shellys aendert sich nichts, sie koennen kein
MQTT und bleiben ganz bei HTTP.
Zweitens haette der Runner den Wechsel gar nicht bemerkt. Sein
Fingerabdruck fuers Neuladen zaehlt die Zeilen von actor_states, und ein
Discovery-Lauf schreibt die Zeilen um, statt neue anzulegen - gleiche
Zeilenzahl, keine Reaktion, bis zum naechsten Neustart. Jetzt gehen url und
value_path als CRC-Summe mit ein, ebenso die command_url der Kommandos.
Nach dem naechsten Suchlauf faellt damit das Polling fuer vier Geraete weg:
34 der 35 HTTP-Messwerte wandern zum Broker, gefragt wird nur noch der
Handtuchtrockner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Verbindung zur solarLog-Datenbank steht den ganzen Tag ungenutzt herum
und wird vom Server irgendwann geschlossen; das Lesen der Sonnenzeiten war
dann der erste Zugriff, der ins Leere lief. Ein ping(reconnect=True) davor
holt sie zurueck.
Ausserdem stehen die abgefangenen Fehler jetzt mit %r im Protokoll: bei
manchen Ausnahmen ist str() leer, und dann stand dort eine Warnung ohne
Grund.
logrotate dreht monatlich statt taeglich, dafuer schon ab 10 MB.
Bewegt eine Automatik eine Jalousie, stand ihr alter Stand bis zu fuenf
Minuten in actor_states - so lange dauert es bis zur naechsten Runde von
poll_tahoma. Im Raum-Modal sah man dann eine Jalousie, die es so nicht
mehr gab.
Der Versandfaden wartet nach dem Kommando jetzt das Ende der Fahrt ab und
meldet den Stand des Geraets zurueck; geschrieben wird er wie alles andere
im Hauptfaden. Gewartet wird nur auf core:MovingState, ohne Zielwerte -
welche das waeren, weiss an dieser Stelle niemand, und nicht jedes
Kommando loest eine Fahrt aus. Dass der Faden dabei steht, ist gewollt:
das naechste Kommando an dieselbe Jalousie darf ohnehin erst nach der
Fahrt kommen.
Transport.nachlesen() gibt es fuer alle, liefert aber nur bei Tahoma
etwas. MQTT-Geraete melden sich von selbst, HTTP und WLED werden jede
Minute gefragt.
Nebenbei: das Lesen aller Zustaende eines Geraets steckt jetzt in
_zustaende(), die Zuordnung zu den Messwert-Nummern in _zuordnen().
zustaende_lesen() und die Warteschleife der Kugelschreiber-Mechanik
benutzen beide dasselbe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Signatur, an der er ein Neuladen festmacht, bestand aus Zeilenzahlen
und MAX(changed). Beides bleibt gleich, wenn in der Weboberflaeche nur die
Uhrzeit einer Bedingung verstellt wird: gespeichert wird durch Loeschen
und Neueinfuegen, und automations.changed ruehrt sich nicht, weil sich in
der Zeile selbst nichts aendert. Der Runner lief dann bis zum naechsten
Neustart mit der alten Uhrzeit weiter - ohne dass irgendwo etwas
schieflief, er wusste es schlicht nicht besser.
Jetzt geht der Inhalt mit ein, als Summe der CRC32 je Zeile. Billig genug
fuer die Pruefung alle 30 Sekunden. Neu dabei sind die Aktionsparameter,
die vorher gar nicht drinstanden - eine geaenderte Zielhoehe einer
Jalousie schlug also ebenso wenig durch.
Nachgestellt mit einer Automatik ohne Aktionen: vorher blieb eine reine
Bedingungsaenderung wirkungslos, danach loeste sie zwei Sekunden nach der
Zielminute aus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Erster Stand der Hintergrundprozesse, die auf der Synology unter
/volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler
je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der
AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins
Web-Verzeichnis.
Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die
nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie
von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot),
zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das
Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat.
Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py,
gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso
gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte.
Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein
pip, sie muessen neben den Skripten liegen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>