- 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>
- gatherSkodaData.py liest bis zu drei API-Schluessel und nutzt sie reihum;
das Kontingent gilt gemessen fuers ganze Konto, daher feste Aufteilung
14 Abruf / 4 Befehle / 2 Reserve
- nach einem Befehl aus der Weboberflaeche wird einmal ausser der Reihe
nachgesehen
- Wallbox-Verlauf waehrend einer Ladung nach skoda_ladepunkte, dazu
skoda_ladepunkte_nachtragen.py fuer Ladungen aus EnergyFlow
- evPlug ist jetzt ein Wahrheitswert: bool("no car") war immer True
- skoda.conf.example beschreibt das gemeinsame Format
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>
wecker.py hat genau das getan, was der AutoAction-Runner ohnehin kann -
nur mit allem doppelt: eigener Datenbank alarm, eigenem Feiertags- und
Ferienkalender neben calendar_days, eigenem UDP-Log auf Port 13377,
eigener Endlosschleife und einem Startskript samt naechtlichem Neustart
um drei.
Punkt fuer Punkt hatte der Runner die bessere Fassung schon: die Zeit
als Bedingung "um 05:50" mit Nachholfenster statt eines Vergleichs im
20-Sekunden-Takt, weekdays als Bitmaske statt sieben Spalten,
on_holiday/on_vacation gegen calendar_days statt gegen einen zweiten
Kalender, die steigende Flanke statt einer wecked-Spalte, und den
Versand mit einem Faden je Geraet statt fuenf HTTP-Versuchen im Abstand
von vier Sekunden.
Umgezogen ist die eine Weckzeit, die scharf war: 05:50, Mo-Fr, nicht in
den Ferien, nicht an Feiertagen, WLED-Preset 5 auf MenasHimmel. Die drei
anderen Zeilen in alarmtime standen auf onoff = -1.
wecker_zu_automatik.sql legt die Automatik an und beschreibt im Kopf,
wie dasselbe im Dashboard von Hand geht.
Die Datenbank alarm und der Abschnitt [alarm] in der config.ini bleiben
vorerst stehen - sie liest nur niemand mehr.
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.
Dieselben zwei Korrekturen wie im Web-Repo (Smart-Dashboard, fe8602a) -
beide Versender muessen sich hier einig sein.
nachlesen() brach auf "core:MovingState ist false" ab. Die Box meldet das
Ende der Fahrt aber, bevor Hoehe und Neigung darauf nachgezogen haben;
festgehalten wurde dann der Wert von kurz davor. Jetzt zaehlen zwei
gleiche Ablesungen hintereinander.
Der Umweg der Kugelschreiber-Mechanik haengt nicht mehr am Neigungswert
(ueber 30 %), sondern daran, ob der Befehl die Hoehe mitsetzt: eine
Hoehenfahrt rastet die Lamellen um, danach muss die Neigung ueber 0 %
wieder angefahren werden, bei jedem Winkel. Eine Aktion, die nur die
Neigung setzt, faehrt direkt; eine, die nur die Position setzt, hat kein
Neigungsziel und kann nichts nachfahren - dafuer gibt es
"Position+Neigung".
Geprueft mit sieben Faellen gegen diese Datei: Position+Neigung 10 und 80
nehmen den Umweg, reine Neigung 10 und 80 gehen direkt, "Zu" wird zu
100/100 mit Umweg, ein Rollladen ohne Lamellen bleibt bei "down".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Bisher gab es nur ein README zum Runner. Wer wissen wollte, welches
Skript eine Zahl liefert und wer sie weiterverwendet, musste sich das aus
Importen, Topic-Namen und Startskripten zusammensuchen - und hatte am
Ende trotzdem nicht gesehen, dass fuer einen Teil der Messwerte gar kein
Skript zustaendig ist.
Zwei Mermaid-Diagramme, geteilt am MQTT-Broker: das erste zeigt, woher
die Zahlen kommen, das zweite, wer sie benutzt. Dazu Tabellen, wem
welches Topic und welche Datenbank gehoert und was wann startet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Erster Teil der Abloesung von auto_watering.py im Web-Repo. Das Skript
dort entschied bisher selbst: stuendlich den Regen holen, mit Schwellen
aus einer ZONES-Tabelle vergleichen, schalten. Kuenftig entscheidet der
AutoAction-Runner, und die Schwellen stehen im Editor statt im Quelltext.
Dieser Sammler liefert dafuer nur noch die Zahl. Alle dreissig Minuten
holt er die Niederschlagsdaten von Open-Meteo und legt sie als retained
JSON nach Wetter/Regen:
{"tage1": 0.4, ..., "tage7": 3.4, "stand": "04.09.2026 09:00:00"}
"tageN" ist die Summe ueber N Tage, wobei der heutige, noch laufende Tag
als letzter zaehlt - dieselbe Rechnung wie accumulated_rain() im alten
Skript. Der heutige Tag kommt aus den Stundenwerten und nicht aus der
Tagessumme: die traegt die Vorhersage fuer den Rest des Tages mit, und
Regen, der erst am Abend kommen soll, hat eine Bewaesserung am Morgen
nicht zu verhindern.
Veroeffentlicht werden immer alle sieben Fenster. Welche davon als
Messwert auftauchen, entscheidet allein das Discovery-Modul im Web-Repo -
so muessen sich die beiden Repositorien nicht ueber eine Fensterliste
einigen, und eine vierte Zone mit einem vierten Zeitraum kostet hier
keine Aenderung.
Ein Dauerlaeufer und kein Cronjob, weil der alte Wert nicht einfach
stehenbleiben darf, wenn Open-Meteo ausfaellt: eine retained "0,0 mm" von
gestern wuerde den Runner bei naechster Gelegenheit giessen lassen. Der
letzte Wert gilt deshalb nur, solange er juenger als max_alter_stunden
ist, danach werden leere Werte geschickt - eine Bedingung auf einem
leeren Messwert gilt im Runner als nicht erfuellt. auto_watering.py tat
dasselbe, nur mit sys.exit(1).
Der Runner selbst bleibt unangetastet: Wetter/Regen ist fuer ihn ein ganz
gewoehnliches MQTT-Geraet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Schreibweg wattpilot/properties/<schluessel>/set war unbenutzbar. Die
Bibliothek reichte Zahlen als Zeichenkette an die Wallbox weiter
("value must be uint8_t"), griff bei unbekannten Namen und bei
Eigenschaften ohne rw-Angabe ungeprueft zu - ftt und fte haben keine, ein
Klick auf die Ladeplanung haette den paho-Callback getoetet - und meldete
nichts zurueck.
Jetzt bekommt der Wert den Typ aus der API-Beschreibung, so dass die
Wallbox 'lmo = 4' genauso nimmt wie 'lmo = Awattar'. Unbekanntes und
Schreibgeschuetztes wird abgelehnt statt zu werfen, und das Ergebnis geht
auf <schluessel>/result - genau wie es der go-e tut. Damit laesst sich das
Dashboard auf einen Weg fuer beide Wallboxen umstellen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'lot' steht in der API-Beschreibung als jsonType integer, die Firmware
schickt aber ein Objekt. Die Bibliothek reicht es unveraendert an paho
weiter, paho nimmt nur Skalare - und die Ausnahme riss dieselbe Schleife
mit wie der KeyError auf unbekannte Felder. Im rotierten Log machte
dieser zweite Fehler 1.264.882 von 2.985.071 Zeilen aus.
mqtt_get_encoded_property wird jetzt ebenfalls ersetzt: was nach der
Kodierung kein Skalar ist, geht als JSON hinaus, einmalig mit Warnung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Wallbox schickt 34 Statusfelder, die die API-Beschreibung von
wattpilot 0.2 nicht kennt. Die Bibliothek schlug jedes Feld ungeprueft
nach und lief in einen KeyError, der die restliche Schleife mitriss -
alle Werte hinter dem unbekannten Feld derselben Nachricht kamen nie
beim MQTT-Broker an. Im Dauerbetrieb traf es alle 15 Sekunden fhz, lps
und tpcm, nach einem Verbindungsaufbau deutlich mehr; sichtbar war es
nur als Fehlerpaar 'clea' im Log, rund ein MB am Tag.
wattpilot_bruecke.py ersetzt die betroffene Funktion zur Laufzeit und
ueberspringt unbekannte Felder, jedes einmal mit einer Warnung. Die
Reparatur steht hier und nicht in site-packages, weil eine
Neuinstallation sie dort spurlos zurueckdrehen wuerde und wattpilot 0.2
seit Mai 2022 die letzte Veroeffentlichung ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wattpilotshell.log hatte 329 MB erreicht - die wattpilotshell meldet
seit Monaten im Sekundentakt denselben Verbindungsfehler, und rotiert
wurde nie. logs_rotieren.sh mit logrotate.conf raeumt das jetzt taeglich
auf: vierzehn Staende, alles ueber 20 MB sofort, damit ein Prozess in
einer Fehlerschleife nicht an einem Tag das Volume fuellt. Der erste Lauf
hat aus den 329 MB 61 KB gemacht, verloren ist nichts.
Nicht in /etc/logrotate.d abgelegt - das ist DSM-Gebiet und beim
naechsten Systemupdate weg. Der Zustand kommt aus einer eigenen Datei
statt aus /var/lib/logrotate, wo der von DSM liegt.
Die Startskripte leiten dafuer mit ">>" um statt mit "&>". Die Prozesse
laufen monatelang durch und sollen fuer eine Rotation nicht neu starten
muessen, deshalb copytruncate - der Inhalt wird weggeschrieben und die
Datei geleert, waehrend sie offen bleibt. Ohne Anhaengemodus schriebe der
Prozess danach an seiner alten Stelle weiter und die Datei bekaeme vorn
ein Loch aus Nullbytes.
Nebenbei behoben: startWecker.sh leerte seine Logdatei bei jedem Start,
und gestartet wird der Wecker jede Nacht um drei. Was er am Vortag
gemeldet hatte, war morgens nicht mehr nachzulesen.
Die Skripte bekommen ausserdem ihr Ausfuehrungsrecht in den Index - ueber
die Windows-Freigabe sieht Git keine Unix-Rechte, ein frischer Checkout
haette sie nicht starten koennen.
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>
zeit.py las eine Stempeluhr ueber die serielle Schnittstelle aus und schrieb
in die Tabelle zeiten. Kein Startskript rief es auf, kein Modul importierte
es. Damit faellt auch pyserial weg - ausser zwei Testdateien in sunspec2,
die nicht ausgefuehrt werden, hat serial keinen Nutzer mehr.
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>