Commit Graph

5 Commits

Author SHA1 Message Date
admin 142a621066 Dauerbetrieb: Autostart (main.py) und Watchdog
MicroPython fuehrt main.py nach jedem Reset selbst aus -- nach Stromausfall oder
Watchdog-Neustart laeuft die Anzeige damit ohne Notebook wieder an. Bisher gab es
die Datei bewusst nicht, weil am Aufbau gearbeitet wurde.

**Das Abbruchfenster ist kein Komfort, sondern die Notbremse.** Ohne es waere ein
Geraet, dessen Programm beim Start haengt, nur noch durch Neuflashen erreichbar:
Die Endlosschleife belegt die REPL sofort nach dem Reset. main.py wartet deshalb
erst AUTOSTART_DELAY (3 s), in denen Ctrl-C zurueck an die REPL fuehrt -- dann
laeuft auch kein Watchdog.

Watchdog (WDT_TIMEOUT = 120 s): startet das Geraet neu, wenn die Poll-Schleife
stehenbleibt. Gefuettert wird zu Beginn *jedes* Durchlaufs, auch nach einem
Fehler -- "KEIN LINK" ist kein Stillstand, die Schleife laeuft ja weiter. Der
Wert muss ueber allem liegen, was regulaer blockieren darf (WLAN 20 s,
Montierung 3 s, dazu ein MQTT-Versuch); knapper waere er eine Neustart-Schleife.

Ein gestarteter Watchdog laesst sich nicht mehr abschalten. Deshalb legt ihn nur
main.py an -- im Handbetrieb ueber die REPL startete das Geraet sonst neu,
waehrend man nachdenkt.

Weiteres:
- Nach einem Watchdog-Neustart meldet main.py das beim Start
  (machine.reset_cause()), sonst saehe er aus wie ein Stromausfall.
- Eine Ausnahme faengt main.py ab und startet nach RESTART_DELAY (10 s) neu,
  statt in die REPL zu fallen und die Anzeige einzufrieren. Die Pause verhindert
  eine Neustart-Schleife im Sekundentakt.
- Autostart faehrt mit Helligkeitsregelung (with_ldr=True, Nutzerentscheidung).

Am Geraet bestaetigt: Autostart nach Reset laeuft durch (Watchdog aktiv, WLAN,
MQTT), ein absichtlich herbeigefuehrter Stillstand loest den Watchdog aus und das
Geraet kommt von allein wieder hoch -- mit der Meldung "Vorheriger Lauf wurde vom
Watchdog beendet". Ctrl-C im Startfenster gibt die REPL frei.

Beim Geraetetest aufgefallen: make_watchdog wurde ohne log aufgerufen, im
Startprotokoll stand also nichts vom Watchdog. Behoben und durch einen Test
festgehalten. 178 Tests gruen (neu: test_main.py).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:41:33 +02:00
admin 41f5eb1821 Per MQTT gesetzte Helligkeit sofort an die Anzeige senden
Am echten Broker aufgefallen: "hell_prozent = 100 uebernommen" stand im Log, die
Anzeige blieb aber auf 50 %. Der Wert landete korrekt in settings.json und der
Dimmer las ihn per reload() auch nach -- nur ging kein Helligkeitstelegramm raus.

Grund: Ein Telegramm wird nur beim *Wechsel* der Stufe gesendet (so war es schon
im alten MSP430-Programm gedacht, und fuer die LDR-Regelung ist das richtig).
Aendert man die Helligkeit von aussen, waehrend die Stufe dieselbe bleibt, gibt es
keinen Wechsel -- die Aenderung waere erst beim naechsten zufaelligen
Stufenwechsel sichtbar geworden.

run_esp32.main setzt nach dem reload() jetzt die Helligkeit der aktuellen Stufe
neu. Ohne Dimmer (kein LDR) gilt weiterhin direkt hell_prozent.

test_mqtt.TestHelligkeitWirdSofortSichtbar haelt das fest. Gegenprobe gemacht:
mit der alten Fassung schlaegt der Test fehl (keine Helligkeit gesendet), mit der
neuen geht genau ein Telegramm mit dem gesetzten Wert raus.

Am Geraet bestaetigt, Broker "nuccy": Verbindung steht, Status (ra/dec/ldr/
helligkeit/link/online) kommt retained an, Einstellungen werden uebernommen,
ungueltige Werte (500, Text) abgelehnt ohne settings.json anzufassen, und die
Anzeige reagiert jetzt sichtbar auf 100 % -> 15 % -> 50 %. 145 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:59:09 +02:00
admin bf39b75ac1 MQTT: Einstellungen empfangen, Zustand veroeffentlichen
Topics (Praefix aus mqtt_config.py, Vorgabe "grossanzeige"):

    <PREFIX>/set/hell_prozent      60      eingehend, schreibt settings.json
    <PREFIX>/set/dunkel_prozent     5
    <PREFIX>/set/schwelle        1800
    <PREFIX>/set/hysterese        150

    <PREFIX>/status/ra        18h36m56s    ausgehend, retained
    <PREFIX>/status/dec      +38°47'01"
    <PREFIX>/status/ldr            2453
    <PREFIX>/status/helligkeit       50
    <PREFIX>/status/link              1
    <PREFIX>/status/online            1    mit Last Will auf 0

Eine empfangene Einstellung wird sofort wirksam: dieselbe Pruefung wie sonst
(settings.update), dann laedt der laufende BrightnessController sie per reload()
nach -- kein Neustart noetig.

Leitgedanke des Moduls: **MQTT darf die Anzeige nie aufhalten.** Die Anzeige ist
der Zweck des Geraets, MQTT ist Beiwerk. Deshalb faengt mqtt.py jeden Fehler
selbst ab und meldet ihn nur:

- Broker nicht erreichbar -> Versuch scheitert, Schleife laeuft weiter.
  Wiederholung mit wachsendem Abstand (5..120 s), sonst kostet ein dauerhaft
  toter Broker in jedem Schleifendurchlauf Zeit.
- Verbindungsabriss -> beim naechsten Senden/Empfangen bemerkt, Verbindung wird
  verworfen und spaeter neu aufgebaut. Danach geht der gesamte Status erneut
  raus, damit der Broker nicht auf veralteten Werten sitzenbleibt.
- Unsinniger Wert von aussen -> verworfen, settings.json bleibt unberuehrt.
- Geraet faellt aus -> Last Will meldet online=0. Ohne das bliebe online=1
  stehen, obwohl niemand mehr da ist.

Der Empfang blockiert nicht (check_msg). Statuswerte gehen nur bei Aenderung
raus -- die Koordinaten aendern sich staendig, LDR und Helligkeit kaum.

mqtt_config.py ist optional und gitignored (Vorlage mqtt_config_example.py);
fehlt sie, laeuft alles wie bisher ohne MQTT. deploy.sh weist nur darauf hin.

test_mqtt.py haengt einen Fake-Broker ein und prueft auch die Faelle, die man
mit einem echten Server schwer herbeifuehrt: Abriss beim Senden, Muell im Topic,
Broker der nicht antwortet. 143 Tests gruen.

Am Geraet bestaetigt ist bisher der Fall OHNE Broker: mqtt.py importiert,
connect_from_config liefert None, ein unerreichbarer Broker wirft keine
Ausnahme. Der Test gegen einen echten Broker steht noch aus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:48:41 +02:00
admin e8a5b5d5e3 LDR-Schwellen am realen Aufbau gemessen statt vom MSP430 uebernommen
Gemessen am XIAO ESP32-C3 mit dem LDR an GPIO2, je 200 Werte, Median
(Angaben nach raw_to_ldr, also in Zaehlrichtung "gross = hell"):

    abgedeckt      roh 4095 (ADC-Anschlag)  ->    0
    Raumlicht      roh 1642                 -> 2453
    angeleuchtet   roh  543                 -> 3552

Zwei Fehler in den alten Werten, die erst die Messung zeigt:

- Stufe 3 (100 %) war unerreichbar. Sie begann bei 3650, selbst eine Taschenlampe
  direkt auf dem Sensor kam nur auf 3552. Die Anzeige haette nie voll aufgedreht.
- Die Hysterese von 30 am untersten Uebergang lag unter dem Rauschen. Der Rohwert
  streut bei Raumlicht um +/-270, nach dem gleitenden Mittel bleiben rund +/-32
  (1 Sigma) -- die unterste Stufe haette geflattert.

Neu: Mitten 1200/2400/3200, Hysterese einheitlich 150. Die Aufteilung haelt den
5-%-Bereich bis 1200 (Nutzerwunsch "nachts laenger dunkel", schont die
Dunkeladaption der Beobachter). Die gestaffelten Hysteresen des alten Programms
entfallen -- die gemessene Streuung ist an allen Grenzen dieselbe.

Bekannte Grenzlage, in test_brightness festgehalten: Raumlicht (2453) liegt nur
53 Zaehler ueber HELL_HIGH und damit innerhalb der Hysterese, faellt also auf
Stufe 1 statt 2. Ob das so gewollt ist, entscheidet sich in der Sternwarte.

Am Geraet bestaetigt: Regelung laeuft stabil ohne Flattern, Abdecken schaltet
herunter, Freigeben wieder hoch, Helligkeitstelegramm wird quittiert. Die
Schwellen sind aber am Schreibtisch gemessen -- with_ldr bleibt deshalb auf
False, bis sie vor Ort bestaetigt sind. 101 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:21:08 +02:00
admin 42fc7f52d4 Portierung auf den XIAO ESP32-C3 (MicroPython)
Die Kette laeuft auf dem Geraet gegen den Mock auf dem Mac. Getestet mit
MicroPython 1.28.0 (ESP32_GENERIC_C3): alle Module importieren, Telegrammbau
und Koordinaten-Formatierung sind byte-identisch zum Mac (inkl. Gradzeichen),
Poll-Schleife 22 Abfragen in 20 s ohne Aussetzer, Ausfall und Reconnect
verhalten sich wie vorgesehen. Offen bleibt allein die RS232-Strecke zur
Anzeige -- der Steckbrettaufbau mit MAX3232 fehlt noch.

Gemeinsamer Kern statt zweier Programme:
- displayloop.py: die Poll-Schleife, herausgeloest aus run_display. Mac und
  ESP32 fahren jetzt denselben Ablauf; drumherum liegt nur die Kommandozeile
  (run_display.py) bzw. das WLAN-Setup (run_esp32.py).
- ticks.py: Timeouts fuer beide Plattformen. time.ticks_ms() laeuft nach ~12
  Tagen ueber; das bisherige "jetzt + Timeout" haette eine Warteschleife
  danach tagelang haengen lassen. Jetzt ueber ticks_diff.
- wifi.py, run_esp32.py, wifi_config.py (gitignored, Vorlage dabei), deploy.sh.

Pinbelegung aus dem KiCad-Schaltplan uebernommen, nicht geraten: UART1 mit
TX=GPIO21 (Pad 7/D6) und RX=GPIO20 (Pad 8/D7) ueber den MAX3232, LDR an
GPIO2 (Pad 1/A0). UART0 bleibt die REPL.

LDR-Kennlinie ist auf der Platine umgekehrt: der Teiler lautet
3V3-[2k]-A0-[LDR]-GND, die Spannung faellt also mit steigender Helligkeit --
andersherum als beim MSP430, auf den die Schwellen zugeschnitten sind. Wird
im Code gespiegelt (config.LDR_INVERTED, brightness.raw_to_ldr). Die
Schwellen selbst muessen am realen Aufbau noch nachgemessen werden.

Am Geraet gefundene Fehler, die den Attrappen-Tests entgehen mussten, weil
CPython sie nicht hat:
- MicroPython kennt kein str.ljust (auch kein rjust/center/zfill).
  display._emit_line fuellt von Hand auf.
- NOLINK_LINES hatte 10 Zeichen bei 9 Zeichen Zeilenbreite und erschien als
  "MONTIERUN". Jetzt "KEIN LINK"/"TELESKOP".
Dagegen: TestMicropythonVertraeglichkeit prueft die deployten Dateien statisch
auf solche Methoden und auf modulweites argparse/threading und haelt die
Dateiliste mit deploy.sh synchron; test_display.py prueft die Breite fester
Anzeigetexte.

Weitere Portabilitaet: mount_client nutzt getaddrinfo und eine Sendeschleife
statt sendall (MicroPython laesst dessen Verhalten mit Timeout offen);
argparse in brightness wandert in die Funktion; migra._digits und die
Hex-Ausgabe verlassen sich nicht mehr auf %0*d bzw. bytes.hex(). Beides kann
1.28 zwar doch -- am Geraet nachgeprueft, die Kommentare sagen es jetzt richtig.

Widerspruch in der Dokumentation bereinigt: die Zeichenbreite stand an vier
Stellen als 9 oder 10. Gemessen sind 9; die Erklaerung duerfte sein, dass die
Sperrschrift 7 px pro Zelle braucht (9*7 = 63 von 64 px) und nicht 6, wie es
eine 5x7-Matrix mit 1 px Luecke waere. Nachgeprueft ist das nicht -- dafuer
gibt es jetzt "probe.py pitch", das den Auto-Vorschub gegen feste
Pixelabstaende stellt, statt am Umbruch abzuzaehlen.

mount_mock.py --lan bindet auf 0.0.0.0 und nennt die einzutragende LAN-IP,
damit der ESP32 den Mock erreicht.

92 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 18:51:45 +02:00