Seit der Einzelpositionierung blitzten bei jeder Aktualisierung willkuerliche
Pixel auf, in beiden Zeilen. Ursache ist die Telegrammlaenge: Eine volle Zeile
kostet mit einzeln gesetzten Zeichen rund 100 Bytes statt 20, bei 19200 Baud
und 8E1 (11 Bit je Byte) dauerte ein kompletter Bildwechsel damit rund 300 ms.
Die Anzeige stellt schon waehrend des Empfangs dar, also war der Aufbau zu
sehen -- im Sekundentakt ein Drittel der Zeit.
Von einer Sekunde zur naechsten aendert sich aber fast nichts: "18h36m56s" ->
"18h36m57s" ist eine einzige Ziffer, die Deklination steht meist still.
show_lines vergleicht deshalb gegen den zuletzt dargestellten Inhalt und
schreibt nur die betroffenen Zellen; eine unveraenderte Zeile erzeugt gar kein
Telegramm mehr. Am Geraet gemessen: 306 ms -> 66 ms je Bildwechsel.
Dabei am Geraet aufgefallen und mitbehoben: Dem "s" hinter den Sekunden fehlten
die linken Pixel, dem "m" hinter den Minuten ebenso -- beides Zeichen direkt
rechts von einer Stelle, die sich gerade geaendert hatte. Die Anzeige malt je
Zeichen 7 px breit (daher auch der 7-px-Auto-Vorschub), gesetzt wird aber auf
6-px-Raster: Ein neu geschriebenes Zeichen loescht die erste Pixelspalte seines
rechten Nachbarn. Beim vollstaendigen Neuaufbau fiel das nie auf, weil der
Nachbar gleich danach ohnehin neu gemalt wurde. _zelle_faellig zieht ihn jetzt
mit.
Weitere Details:
- clear() verwirft den gemerkten Inhalt, sonst haelt der Vergleich Zellen
faelschlich fuer vorhanden und die Anzeige bliebe teilweise leer.
- Schlaegt ein Telegramm fehl, wird der gemerkte Inhalt der Zeile verworfen --
was angekommen ist, ist dann ungewiss.
- Der Zeichensatz wird nur noch bei der ersten Ausgabe einer Zeile gesetzt. Das
spart nicht nur Bytes: Jeder Wechsel beschaedigt die Home-Zelle. Findet doch
einer statt (neues Gradzeichen), wird die oberste Zeile vollstaendig
nachgezogen.
Zu beiden Befunden gibt es Tests mit Gegenprobe (mit der alten Fassung schlagen
sie fehl). 155 Tests gruen, am Geraet bestaetigt: ruhig, keine fehlenden Pixel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
Die vier abgestuften Helligkeiten des alten MSP430-Programms (5/15/50/100 %)
entfallen. Fuer die Sternwarte genuegt der Wechsel Tag/Nacht: unterhalb der
Schwelle laeuft die Anzeige mit dunkel_prozent, oberhalb mit hell_prozent.
Die Werte stehen nicht mehr im Code, sondern in settings.json auf dem Geraet --
Vorbereitung fuer die spaetere MQTT-Anbindung:
settings.load() # mit Vorgaben aufgefuellt
settings.update({"hell_prozent": 60}) # aendern und speichern
Vorgaben: dunkel 5 %, hell 50 %, Schwelle 1800, Hysterese 150. Die Schwelle liegt
mit Abstand zwischen den gemessenen Lagen (abgedeckt 0, Raumlicht 2453).
Beibehalten, weil die Messung vom 27.07. sie verlangt: der gleitende Mittelwert
und eine Hysterese um die Schwelle. Der ADC rauscht mit +/-32 (nach Mittelung,
1 Sigma) -- ohne Hysterese wuerde die Anzeige am Umschaltpunkt springen.
settings.py prueft jeden Wert beim Laden UND beim Schreiben auf Typ und Bereich.
Das ist der eigentliche Zweck: Mit MQTT kommen die Werte von aussen, und ein
Helligkeitswert von 500 % oder eine Schwelle von -1 wuerde die Anzeige sonst
unbrauchbar machen. Eine fehlende oder beschaedigte Datei faellt auf die Vorgaben
zurueck, ein einzelner kaputter Wert verwirft nur diesen -- ein Geraet in der
Sternwarte soll sich nicht an seiner Konfigurationsdatei aufhaengen.
settings.json wird von deploy.sh bewusst NICHT mitkopiert und steht in
.gitignore: Es ist Laufzeitzustand, kein Quellcode. Sonst waeren die
eingestellten Werte nach jedem Deployen weg.
Am Geraet bestaetigt: schreiben, wiederlesen, Controller uebernimmt die Werte,
beide Helligkeiten auf der Anzeige sichtbar. 119 Tests gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Die RS232-Strecke ESP32 -> MAX3232 -> Anzeige ist am Geraet bestaetigt; damit
war "probe.py pitch" moeglich und hat die offene Zeichenbreiten-Frage geklaert.
Das Ergebnis korrigiert eine Annahme: Bisher stand CHARS_PER_LINE = 9, abgezaehlt
am Zeilenumbruch. Gemessen wurde damit aber der Auto-Vorschub der Sperrschrift
(7 px), nicht die Zeichenbreite. Die Zeichenmatrix ist 6 px breit -- zehn Ziffern,
einzeln auf x = i*6 gesetzt, stehen sauber getrennt nebeneinander.
- display._emit_line positioniert jedes Zeichen selbst, statt den Auto-Vorschub
laufen zu lassen. CHARS_PER_LINE = 10, CELL_WIDTH = 6.
- Die Deklination passt dadurch in voller Form: +38°47'01" statt +38°47'01.
- show_lines sendet ein Telegramm je Zeile. Mit Einzelpositionierung kaemen beide
zusammen auf 224 der erlaubten 230 Bytes -- zu wenig Reserve fuer den
Dauerbetrieb. Die Reihenfolge bleibt unten vor oben (Home-Zelle).
- Zeilen werden zentriert (CENTER_LINES, LINE_X). Der Zeichenblock bleibt dabei
gleich breit und an derselben Stelle, damit keine Reste stehen bleiben.
- probe.py kommt mit aufs Geraet: Die Anzeige haengt jetzt am ESP32, nur von dort
laesst sich die Geometrie noch ausmessen.
Die Tests pruefen nicht mehr auf zusammenhaengenden Text im Telegramm -- den gibt
es nicht mehr -- sondern rechnen ueber die Cursor-Sequenzen zurueck, was wirklich
auf welcher Zelle landet. 97 Tests gruen, Kette am Geraet im Sekundentakt bestaetigt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Die GM4000 laeuft in Niedrigpraezision (keine Sekunden): RA HH:MM.T,
DEC sDD°MM. Anzeige daran angepasst:
- config.HIGH_PRECISION (Vorgabe False) steuert die DEC-Darstellung:
False -> ohne Sekunden (-00°55'), True -> mit Sekunden (+38°47'01)
- RA immer mit Sekunden (11h36m54s); bei Niedrigpraezision aus den
Zehntel-Minuten abgeleitet (6-Sekunden-Schritte), kein Dezimalpunkt
(der wirkt in der Sperrschrift zu luftig)
- coords.format_ra/format_dec nehmen high_precision=
- MOUNT_HOST auf die echte IP (192.168.1.115)
Hinweis: Das 'ß' in der Konsole ist nur der Rohwert (Grad-Byte 0xDF);
auf der Anzeige erscheint korrekt der Gradring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- brightness.py: BrightnessController + CLI, portiert aus DoDimmen_V/main.h
(gleitender Mittelwert, Hysterese). Schwellen 300/2500 aus altem Code,
Prozente 5/15/50/100
- verallgemeinert auf 4 Stufen: 100% jetzt erreichbar (alter Code max 50%);
neue obere Grenze HELL_MAX=3500 (anpassbar), Rest wie im Original
- run_display: --ldr bindet die Regelung in die Hauptschleife ein
(sendet nur bei Stufenwechsel)
- test_brightness.py: Schwellen, Hysterese, Mittelung
- README dokumentiert die Regelung; .gitignore: Hardware/
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- WANT_RESPONSE=True: Anzeige-Antworttelegramm als Handshake, Fehlercode
wird ausgewertet (Rueckweg verifiziert: 02 80 81 80 30 03)
- transport: Empfangspuffer vor jedem Senden leeren, damit read_response
nur die frische Antwort sieht (Stoerbytes zwischen Telegrammen)
- run_display: Anzeige-/Antwortfehler abfangen statt abzustuerzen
- resp_test.py: Pruefwerkzeug fuer den Rueckkanal
- PORT auf /dev/cu.usbserial-120
- .gitignore ergaenzt, __pycache__ aus der Versionierung entfernt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>