28 Commits
Author SHA1 Message Date
adminandClaude Sonnet 5 d6da199df1 Datumsbedingungen: Jahr wird ignoriert, jaehrliche Wiederholung moeglich
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>
2026-09-22 07:36:41 +02:00
adminandClaude Opus 5 813af4e8a2 Tahoma: verlorene Funkbefehle erkennen und nachsenden
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>
2026-09-21 19:47:39 +02:00
adminandClaude Opus 5 b4e3531800 README: gatherForecastData.py aufgenommen
Der fuenfte Prozess, der aktiv etwas abholt - im Bild, in der Tabelle der
geschriebenen Tabellen und in der, die sagt, was wann startet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 14:07:08 +02:00
adminandClaude Opus 5 b40feeea23 Wettervorhersage sammeln und archivieren (Open-Meteo)
gatherForecastData.py holt alle zwanzig Minuten Stunden- und Tageswerte und
schreibt sie nach solarLog (weatherHours, weatherDays, weatherForecastLog,
weatherTilted). Grundlage des neuen Meteogramms im Web-Repository.

Kein Anbau an gatherRainData.py, obwohl es dieselbe Schnittstelle benutzt:
dessen Fehlerregel ist sicherheitsrelevant - es leert bei einer Stoerung
bewusst seine Werte, damit der Runner nicht auf eine alte "0,0 mm" hin
giesst. Hier gilt das Gegenteil, eine Vorhersage von vor zwei Stunden ist
brauchbar und wird nur als alt beschriftet. Zwei gegenlaeufige Fehlerregeln
in einer Schleife waeren eine Falle.

Es wird nichts geloescht. Die Tabellen sind zugleich ein Archiv: aus der
Einstrahlung und EnergyFlow_hourly.pv_kwh soll spaeter eine eigene
Ertragsprognose gerechnet werden. 8.760 Zeilen im Jahr kosten 1,5 MB.

wetterarchiv_nachtragen.py fuellt das Archiv rueckwirkend aus der
ERA5-Schnittstelle von Open-Meteo - 39.264 Stunden ab dem 02.04.2022, dem
Beginn von EnergyFlow_hourly. Damit ist die Korrelation sofort rechenbar
statt erst in einem Jahr.

Sonnenzeiten schreibt es dabei ausdruecklich nicht: die Archiv-Schnittstelle
rechnet alle Zeiten mit dem heute gueltigen Zeitzonenversatz um und meldet
das auch selbst ("utc_offset_seconds: 7200" fuer einen Dezembertag).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 13:54:31 +02:00
adminandClaude Opus 5 e43ef83d47 README: Verweis auf doku/byd.md im Web-Repository
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 12:34:52 +02:00
adminandClaude Opus 5 e1e678f6c0 Haltezeit: erst ausloesen, wenn die Bedingung dabei bleibt
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>
2026-09-21 08:57:14 +02:00
adminandClaude Opus 5 35382a6ae1 Benachrichtigungen: Web Push und E-Mail als Kanaele der Automatiken
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>
2026-09-21 08:37:08 +02:00
adminandClaude Opus 5 0a37d8d763 BYD-Speicher direkt aus der BMU auslesen
gatherBYDData.py fragt die BMU der HVM unter 192.168.16.254:8080 ab
(BE-Connect-Protokoll, nach ioBroker.bydhvs): Ladestand und SOH laut BMU,
128 Zellspannungen, 64 Temperaturen, Spreizung, Ausgleich, Fehlerbits,
Gesamtzaehler. Das Netzwerkmodul startet alle ~102 s neu und bedient nur
die ersten Verbindungen danach - eigene Klopf-Schleife, ein Satz etwa alle
100 s. Werte unter solarManager/byd/#, Historie in byd und byd_zellen.

tbatt kommt jetzt aus der BMU statt fest 0. mqttClient.publish legt ein
einzelnes Dataclass-Objekt in rtData als Untertopics ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 16:16:07 +02:00
adminandClaude Opus 5 46ef245f7c Doku auf den Stand gebracht: Vorabend, skoda.conf, Wecker-Reste
- 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>
2026-09-15 09:53:08 +02:00
adminandClaude Opus 5 8340e8356e Runner: Kalender am Vorabend fuer den folgenden Tag pruefen
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>
2026-09-15 09:33:59 +02:00
adminandClaude Opus 5 8c62455b2c Skoda: mehrere Schluessel, Wallbox-Ladeverlauf, Stecker als 1/0
- 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>
2026-09-15 09:29:57 +02:00
adminandClaude Opus 5 153658df81 Laderate im Log in Watt statt in Prozent angeben
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 09:29:57 +02:00
adminandClaude Opus 5 32e571099b Ferien und Feiertage dreiwertig, dazu eine Tagessperre
"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>
2026-09-10 20:07:36 +02:00
adminandClaude Opus 5 09ce5c56c7 Automatiken koennen andere Automatiken ausloesen
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>
2026-09-09 22:51:15 +02:00
adminandClaude Opus 5 1f1700391d Wecker als Automatik statt als eigener Prozess
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>
2026-09-09 21:12:20 +02:00
adminandClaude Opus 5 cc96e990fa Messwerte je Wert zuteilen, damit Gen2-Shellys ueber MQTT gelesen werden
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>
2026-09-09 00:07:04 +02:00
admin 1e7b1d8b21 Datenbankverbindung vor den Sonnenzeiten pruefen, Logs sparsamer drehen
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.
2026-09-09 00:06:42 +02:00
adminandClaude Opus 5 f643e37676 Ende der Fahrt abwarten, Umweg an die Hoehe binden
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>
2026-09-05 13:35:55 +02:00
adminandClaude Opus 5 797d3dd6d9 Runner liest nach einem Tahoma-Kommando gleich nach
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>
2026-09-05 12:18:20 +02:00
adminandClaude Opus 5 2e155ea4a7 README mit dem Datenfluss der Hintergrundprozesse
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>
2026-09-05 11:54:42 +02:00
adminandClaude Opus 5 64654ac629 gatherRainData.py holt die Regenmenge fuer die Automatiken
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>
2026-09-04 13:38:26 +02:00
adminandClaude Opus 5 5990a8feb2 Bruecke nimmt jetzt auch Kommandos entgegen
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>
2026-09-04 08:36:00 +02:00
adminandClaude Opus 5 ce2a86fa2b Bruecke faengt auch den zweiten Fehler der Wallbox ab
'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>
2026-09-03 20:42:50 +02:00
adminandClaude Opus 5 acb443e27a Wattpilot-Bruecke verwirft keine Messwerte mehr
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>
2026-09-03 19:53:53 +02:00
adminandClaude Opus 5 4a1cd00e6e Logrotation: die Dateien wachsen nicht mehr unbegrenzt
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>
2026-09-03 19:27:48 +02:00
adminandClaude Opus 5 df2765b16a Runner merkt jetzt, wenn eine Bedingung geaendert wurde
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>
2026-09-03 19:27:10 +02:00
adminandClaude Opus 5 05ce194478 Zeiterfassung und pyserial entfernt
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>
2026-09-02 20:55:26 +02:00
adminandClaude Opus 5 79843aa2ae SolarManager unter Versionsverwaltung
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>
2026-09-02 20:46:59 +02:00