Files
grossanzeige/README.md
T
admin 4f7375add3 10 Zeichen pro Zeile: Zellenbreite am Geraet gemessen, Zeilen zentriert
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>
2026-07-27 15:56:15 +02:00

412 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Großanzeige
Ansteuerung einer microSYST **migra SC/MC Seriell** LED-Großanzeige mit den
Koordinaten (Rektaszension / Deklination) einer 10Micron GM4000 HPS II.
Zielgerät ist ein XIAO ESP32-C3, der die Montierung per WLAN abfragt und die
Koordinaten über UART/RS232 auf die Anzeige schreibt. Entwickelt und getestet
wird auf dem Mac (Koordinaten von Hand bzw. von der Montierung, Ausgabe über
einen USB-RS232-Wandler); dieselben Module laufen auf dem ESP32.
## Benutzung
```bash
# Phase 1 -- Koordinaten von Hand:
python3 show_coords.py 12h34m56s +45d12m30s # Koordinaten anzeigen
python3 show_coords.py 12:34:56 -07:15:00 # Trennzeichen sind egal
python3 show_coords.py --clear # Anzeige löschen
python3 show_coords.py 12h34m56s +45d12m30s --dry-run # nur Telegramm als Hex
# Phase 2 -- Koordinaten von der Montierung (10Micron GM4000):
python3 run_display.py # Montierung aus config.py pollen
python3 run_display.py --host 10.0.0.42 # andere IP
python3 run_display.py --mock --move # ohne Teleskop: lokaler Mock, RA läuft
python3 run_display.py --mock --move --ldr 2000 # zusätzlich mit Helligkeitsregelung
python3 mount_mock.py --move # Mock separat starten
# Helligkeit aus einem LDR-Wert setzen:
python3 brightness.py --ldr 1500 # LDR-Rohwert -> Stufe/Prozent -> senden
python3 -m unittest test_migra test_lx200 test_brightness test_display test_esp32
python3 probe.py ruler # Geometrie der Anzeige ausmessen
```
Auf dem ESP32-C3 (siehe Abschnitt [ESP32-C3](#esp32-c3)):
```bash
cp wifi_config_example.py wifi_config.py # WLAN-Zugangsdaten eintragen
./deploy.sh # Dateien auf den ESP32 kopieren
mpremote repl # dann: import run_esp32
# run_esp32.main()
```
Einzige Abhängigkeit auf dem Mac ist `pyserial`, fürs Übertragen zusätzlich
`mpremote`. Auf dem ESP32 laufen die Module ohne jede Fremdbibliothek.
## Aufbau
Alles, was auf beiden Plattformen läuft, ist bewusst abhängigkeitsfrei
gehalten. Was sich unterscheidet, steckt in `transport.py` (UART) und in den
beiden Startprogrammen.
| Datei | Zweck | ESP32 |
|---|---|---|
| `migra.py` | Protokoll: Telegrammrahmen, Prüfsumme, ESC-Sequenzen. | ✓ |
| `transport.py` | UART: `pyserial` (Mac), `machine.UART` (ESP32-C3), `FakeTransport` (Trockenlauf). | ✓ |
| `ticks.py` | Timeouts, die unter CPython und MicroPython gleich funktionieren. | ✓ |
| `display.py` | Treiber: Telegramm senden, Antwort auswerten. | ✓ |
| `coords.py` | Koordinaten einlesen und auf die Zeilenbreite formatieren. | ✓ |
| `config.py` | Port/Pins, Adresse, Geometrie, Helligkeit, WLAN — alles an einer Stelle. | ✓ |
| `displayloop.py` | Die Poll-Schleife: abfragen, formatieren, anzeigen, Fehler abfangen. | ✓ |
| `brightness.py` | Helligkeitsregelung: LDR-Wert → Stufe/Prozent. | ✓ |
| `mount_client.py` | TCP-Client zur GM4000 (LX200). | ✓ |
| `lx200.py` | LX200-Kommandorahmen. | ✓ |
| `wifi.py` | WLAN-Anbindung des ESP32 (verbinden, Verbindung halten). | ✓ |
| `run_esp32.py` | Startprogramm auf dem ESP32: WLAN, Anzeige, Schleife. | ✓ |
| `wifi_config.py` | WLAN-Zugangsdaten, **nicht im Git** (Vorlage `wifi_config_example.py`). | ✓ |
| `run_display.py` | Startprogramm auf dem Mac: dieselbe Schleife mit Kommandozeile. | |
| `show_coords.py` | Testprogramm (Koordinaten von Hand). | |
| `mount_mock.py` | Simulator der Montierung (LX200) fürs Testen ohne Teleskop. | |
| `probe.py` | Testmuster zum Ausmessen von Zeilenbreite und Zeilenposition. | |
| `deploy.sh` | Kopiert die ESP32-Dateien per `mpremote` auf das Gerät. | |
| `test_migra.py` / `test_lx200.py` / `test_brightness.py` / `test_display.py` / `test_esp32.py` | Tests. | |
## Protokoll (Kurzfassung)
Referenz: Benutzerhandbuch `X-M31-9AXX5X-006 (V4.20)`, Kapitel 3.53.9.
**Schnittstelle:** 19200 Baud, 8 Datenbits, **Even Parity**, 1 Stopbit.
9-pol. Sub-D: Pin 2 = RxD, Pin 3 = TxD, Pin 5 = GND.
**Telegramm:**
```
STX | DA | SA | FC | [LEN-H LEN-L] | Data-Unit | [CHK-H CHK-L] | ETX
02 81 80 81 03
```
- `DA`/`SA`: Adressen mit gesetztem Bit 7 → Anzeige = 1 (`0x81`), Host = 0 (`0x80`).
- `FC`: Bit 7 fest 1, Bit 1 = Prüfsumme, Bit 0 = Antwort anfordern.
`0x81` = ohne Prüfsumme, mit Antwort (so fuhr auch das alte MSP430-Programm).
- Ohne Prüfsumme dürfen `LEN-H/L` und `CHK-H/L` **nicht** mitgesendet werden.
- Prüfsumme = Low-Byte der Summe aus `DA, SA, FC, LEN-H, LEN-L` und allen Datenbytes,
als zwei Nibbles mit gesetztem High-Nibble (`0xF0 | nibble`).
**Antwort:** `02 80 81 80 <Fehlercode> 03`, Fehlercode `'0'` = kein Fehler.
Nach Erhalt der Antwort darf sofort das nächste Telegramm folgen — das ist die
Flusskontrolle. Ohne Antwort muss zwischen den Telegrammen gewartet werden
(Empfangstimeout + ca. 150 ms).
**ESC-Sequenzen** (alle Zahlen ASCII-codiert, Position 123 → `'1' '2' '3'`):
| Sequenz | Bedeutung |
|---|---|
| `ESC z NN` / `ESC Z NN` | Zeichensatz NN (`z` = Sperrschrift, gleiche Zeichenbreite) |
| `ESC C xxxyyy` | Cursor auf **Pixel**position x/y, 0/0 ist links oben |
| `ESC A <fg><bg><blink>` | Attribute; Farbe 0 = schwarz, 1 = grün, 2 = rot, 3 = gelb |
| `ESC F <Farbe>` | Anzeige füllen; `ESC F 0` = löschen |
| `ESC H <Farbe><ppp>` | Helligkeit 0100 % |
| `ESC T <+/-><nnn>` | gespeicherten Text ein-/ausblenden |
Folgt auf eine ESC-Sequenz ein Online-Text, muss **`0x1F` als Trennzeichen**
dazwischen. Data-Unit max. 230 Zeichen. `migra.DataUnit` setzt das Trennzeichen
selbst.
## ESP32-C3
### Verdrahtung
Die Pins stammen aus dem Schaltplan (`Hardware/KiCad/Grossanzeige`) und stehen
in `config.py`. Am XIAO ESP32-C3 ist UART1 frei zuordenbar; UART0 bleibt für die
REPL-Konsole über USB reserviert.
| XIAO | GPIO | Netz | weiter zu |
|---|---|---|---|
| Pad 7 (D6) | 21 | TX | MAX3232 `T2IN``T2OUT` → J5.3 → Anzeige RxD (Sub-D Pin 2) |
| Pad 8 (D7) | 20 | RX | MAX3232 `R2OUT``R2IN` ← J5.2 ← Anzeige TxD (Sub-D Pin 3) |
| Pad 1 (D0/A0) | 2 | LDR | Spannungsteiler J2, siehe unten |
| Pad 12 | — | 3V3 | MAX3232 `VCC` |
| Pad 13 | — | GND | gemeinsame Masse |
Die Anzeige will RS232-Pegel, der ESP32-C3 kann nur TTL — der MAX3232 dazwischen
ist zwingend.
### Einrichten
MicroPython für den ESP32-C3 flashen (einmalig; Firmware von
[micropython.org/download/ESP32_GENERIC_C3](https://micropython.org/download/ESP32_GENERIC_C3/)):
```bash
pip install esptool mpremote
esptool.py --chip esp32c3 --port /dev/cu.usbmodem1101 erase_flash
esptool.py --chip esp32c3 --port /dev/cu.usbmodem1101 --baud 460800 \
write_flash -z 0x0 ESP32_GENERIC_C3-*.bin
```
Dann die Zugangsdaten anlegen und alles übertragen:
```bash
cp wifi_config_example.py wifi_config.py # SSID und Passwort eintragen
./deploy.sh # oder ./deploy.sh /dev/cu.usbmodem1101
```
`wifi_config.py` steht in `.gitignore` — das WLAN-Passwort gehört nicht ins
Repository.
### Starten
```bash
mpremote repl
>>> import run_esp32
>>> run_esp32.main() # ohne Helligkeitsregelung
>>> run_esp32.main(with_ldr=True) # mit LDR über den ADC
```
Bewusst **kein** `main.py`: MicroPython startet eine Datei dieses Namens nach
jedem Reset von selbst, und solange am Aufbau gearbeitet wird, ist der manuelle
Start angenehmer. Für den Dauerbetrieb in der Sternwarte reicht später eine
`main.py` mit den zwei Zeilen von oben.
### Was auf beiden Seiten gleich läuft
`displayloop.poll_loop` ist die gemeinsame Hauptschleife — Mac und ESP32 fahren
denselben Ablauf, drumherum liegt nur die Kommandozeile (`run_display.py`) bzw.
das WLAN-Setup (`run_esp32.py`). Fällt das WLAN aus, zeigt die Anzeige
`NOWIFI_LINES`, fällt die Montierung aus, `NOLINK_LINES`; in beiden Fällen läuft
die Schleife weiter und verbindet sich neu.
Stolpersteine, die beim Portieren aufgefallen sind und in den Tests
festgehalten sind (`test_esp32.py`, `test_display.py`):
- **MicroPython hat kein `str.ljust`** (auch kein `rjust`/`center`/`zfill`).
Das fiel erst am Gerät auf — auf dem Mac gibt es die Methode, alle Tests
waren grün. `display._emit_line` füllt jetzt von Hand auf.
`TestMicropythonVertraeglichkeit` sucht solche Aufrufe seitdem statisch.
- **`time.ticks_ms()` läuft nach ~12 Tagen über.** Ein einfaches
„jetzt + Timeout" ließe eine Warteschleife danach tagelang hängen. `ticks.py`
rechnet deshalb über `ticks_diff`, das den Überlauf richtig behandelt.
- **Der UART braucht denselben Puffer-Flush wie pyserial.** `UartTransport.write`
leert den Empfangspuffer vor jedem Senden, sonst sieht `read_response` alte
Störbytes statt der frischen Antwort.
- **Feste Anzeigetexte müssen in `CHARS_PER_LINE` passen.** `NOLINK_LINES` hieß
zuerst „MONTIERUNG" (10 Zeichen) und erschien als „MONTIERUN"; `_emit_line`
schneidet stillschweigend ab. `test_display.py` prüft die Breite jetzt.
(Seit `CHARS_PER_LINE = 10` würde „MONTIERUNG" wieder passen — die Texte sind
aber bei „KEIN LINK"/„TELESKOP" geblieben.)
Was MicroPython 1.28 entgegen der Erwartung **doch** kann (am Gerät nachgeprüft):
`%0*d` mit dynamischer Feldbreite, `%-11s`, `bytes.hex()`, `str.strip`/`lstrip`,
`str.isdigit`.
Ohne Hardware lässt sich der ESP32-Teil trotzdem weitgehend prüfen:
`test_esp32.py` hängt Attrappen für `machine` und `network` ein und fährt die
ganze Kette gegen einen echten Mock — UART-Transport, WLAN, ADC-Auswertung,
Zeitrechnung und die Poll-Schleife samt Ausfallverhalten.
### Gegen den Mock auf dem Mac testen
Solange die Montierung nicht erreichbar ist, spielt der Mock ihre Rolle. Er muss
dafür auf allen Schnittstellen lauschen, sonst kommt der ESP32 nicht heran:
```bash
python3 mount_mock.py --lan --move # gibt die IP aus, die einzutragen ist
```
Auf dem ESP32 dann:
```python
import run_esp32
run_esp32.selftest(host="192.168.178.26") # Bring-up prüfen
run_esp32.main(host="192.168.178.26", fake_display=True) # Dauerschleife
```
`selftest` geht die drei Stufen einzeln durch — WLAN, Montierung, Telegrammbau —
und sagt, an welcher es klemmt. `fake_display=True` schreibt die Telegramme nur
als Hex in die REPL, es braucht also weder Pegelwandler noch Anzeige.
Wenn keine Verbindung zustande kommt, obwohl der Mock lauscht: macOS fragt beim
ersten Mal, ob `python3` eingehende Verbindungen annehmen darf — das muss
erlaubt sein.
### Am Gerät bestätigt (2026-07-26)
XIAO ESP32-C3 mit MicroPython 1.28.0 (`ESP32_GENERIC_C3`), gegen den Mock auf dem
Mac:
- Alle zwölf Module importieren auf dem Gerät.
- Telegrammbau und Koordinaten-Formatierung sind **byte-identisch** zum Mac,
inklusive Gradzeichen über den Zeichensatzwechsel.
- WLAN verbindet sich, `selftest` geht durch alle drei Stufen.
- Die Poll-Schleife lief 20 s im Sekundentakt, 22 Abfragen ohne Aussetzer.
- **Ausfall und Erholung:** Mock gestoppt → „KEIN LINK / TELESKOP" erscheint, die
Schleife läuft weiter; Mock zurück → automatischer Reconnect, und der erste
Wiedereinstieg löscht einmal das Vollbild, sodass kein Rest stehen bleibt.
- UART1 öffnet auf GPIO21/20 mit 19200 8E1, sendet, und der Lese-Timeout greift
sauber (kein Hänger) — allerdings noch **ohne Gegenstelle**.
- ADC auf GPIO2 mit 11 dB Dämpfung liefert Werte.
## Offene Punkte
- **LDR-Schwellen nachmessen.** Die Grenzen in `brightness.py` stammen vom alten
MSP430-Aufbau. Sobald der LDR am ESP32 hängt, die Rohwerte bei Tag und Nacht
ablesen und `HELL_LOW`/`HELL_HIGH`/`HELL_MAX` anpassen. Bis dahin läuft die
Anzeige mit fester Helligkeit (`run_esp32.main()` ohne `with_ldr`).
- **Netzwerk zur echten Montierung.** Die GM4000 steht in `192.168.1.115`, das
Heimnetz des ESP32 ist `192.168.178.x` — der Mac erreicht sie per VPN, der
ESP32 so nicht. Muss geklärt werden, bevor es an die echte Montierung geht.
- **Dauerbetrieb:** Autostart (`main.py`) und ein Watchdog fehlen noch bewusst.
## Gradzeichen
Der Standard-Zeichensatz 0 der Anzeige hat **kein** `°`. Der von der Sternwarte
geladene **Zeichensatz 1** enthält an Position `/` (0x2F) ein hochgestelltes
Gradzeichen (deckt sich mit dem alten Programm, `asciitaf.h`: Code 0xAF → per
`& 0x7F` = 0x2F in Zeichensatz 1). Zur Ausgabe wird für dieses eine Zeichen kurz
auf Zeichensatz 1 umgeschaltet und wieder zurück (`config.DEGREE_*`, `display._emit_degree`).
**Fallstrick:** Ein Zeichensatzwechsel beschädigt die Home-Zelle (0,0) — das erste
Zeichen der obersten Zeile wird zerstört. Deshalb schreibt `show_lines` die Zeilen
**von unten nach oben** (oberste Zeile zuletzt), damit sie diese Zelle überschreibt.
Das gilt weiterhin, seit jede Zeile ihr eigenes Telegramm bekommt: dann entscheidet
die Reihenfolge der Telegramme statt der Reihenfolge innerhalb eines Telegramms.
## Geometrie und Darstellung
- **10 Zeichen pro Zeile zu 6 px** (`CHARS_PER_LINE = 10`, `CELL_WIDTH = 6`), am
Gerät entschieden mit `probe.py pitch 6` (2026-07-27).
Vorher stand hier 9 — das war am Zeilenumbruch abgezählt und maß etwas
anderes, als es zu messen schien. `probe.py pitch` stellt den Auto-Vorschub
der Anzeige gegen feste Pixelpositionen `x = i·cell` und trennt damit beides:
- Der **Auto-Vorschub** der Sperrschrift `ESC z` ist **7 px**. Eine Zeile in
einem Stück geschrieben bricht deshalb nach 9 Zeichen um (9·7 = 63 von 64 px).
- Die **Zeichenmatrix** ist aber nur **6 px** breit. Zehn Ziffern, einzeln auf
`x = i·6` gesetzt, stehen sauber getrennt nebeneinander (60 von 64 px).
Deshalb positioniert `display._emit_line` jedes Zeichen selbst, statt den
Auto-Vorschub laufen zu lassen. Der Preis sind längere Telegramme (rund 10 Byte
je Zeichen statt 1), der Gewinn ein volles Zeichen mehr pro Zeile.
```bash
python3 probe.py pitch 6 # stehen beide Zeilen untereinander -> 6 px/Zelle
python3 probe.py pitch 7 # ... -> 7 px/Zelle
```
- **Ein Telegramm je Zeile.** Früher gingen beide Zeilen in einem Telegramm raus.
Mit Einzelpositionierung kämen sie mit Gradzeichen und Vollbild-Löschen auf
224 der erlaubten 230 Bytes (`migra.MAX_DATA_UNIT`) — zu wenig Reserve für ein
Gerät, das monatelang durchläuft. Getrennte Telegramme halten jedes bei rund
der Hälfte; die Flusskontrolle über das Antworttelegramm macht die zweite
Sendung unproblematisch. Die Reihenfolge bleibt **unten vor oben** (Home-Zelle).
- Die Deklination passt damit in **voller Form mit abschließendem `"`**
(`+38°47'01"`, 10 Zeichen). `coords.format_dec` wählt automatisch die breiteste
Form, die in `CHARS_PER_LINE` passt — bei Niedrigpräzision entsprechend
`+38°47'` ohne Sekunden.
- **Zeilen werden zentriert** (`CENTER_LINES = True`). Das wirkt auf zwei Ebenen:
`LINE_X` rückt den Zeichenblock in die Mitte (10·6 = 60 px lassen je 2 px Rand),
und kürzere Texte werden beidseitig mit Leerzeichen aufgefüllt. Der Block bleibt
dabei immer gleich breit und an derselben Stelle — so überschreibt er bei jedem
Durchlauf denselben Bereich und lässt keine Reste stehen. Ein pixelgenau
mitwandernder Offset könnte das nicht; dafür ist die Zentrierung nur auf eine
halbe Zelle (3 px) genau, wenn die Restbreite ungerade ist.
- **Zyklische Updates flimmerfrei:** `show_lines` löscht nicht bei jeder Aktualisierung,
sondern überschreibt die Zeilen an Ort und Stelle (auf volle Breite aufgefüllt).
`clear=True` nur beim ersten Bild. Wichtig: Zeilen dürfen **nicht breiter** als
`CHARS_PER_LINE` aufgefüllt werden, sonst bricht das Füll-Leerzeichen um und
beschädigt die andere Zeile.
## Präzision (Low / High)
Die GM4000 läuft standardmäßig in **Niedrigpräzision** und sendet keine Sekunden:
RA als `HH:MM.T` (Zehntel-Minuten), DEC als `sDD°MM` (ganze Bogenminuten). Das
Gradzeichen kommt als Byte `0xDF` — auf der Anzeige korrekt als Gradring dargestellt;
in der Terminal-Konsole erscheint der **Rohwert** dieses Bytes als `ß` (rein kosmetisch).
`config.HIGH_PRECISION` steuert die DEC-Darstellung:
- `False` (Vorgabe): DEC ohne Sekunden (`-00°55'`).
- `True`: DEC mit Sekunden (`+38°47'01`) — erst sinnvoll, wenn die Montierung per
`:U#` auf Hochpräzision gestellt ist (schreibendes Kommando, hier bewusst nicht
automatisch gesendet).
Die **RA** wird immer mit Sekunden gezeigt (`11h36m54s`); bei Niedrigpräzision
kommen die Sekunden aus den Zehntel-Minuten und springen daher in 6-Sekunden-Schritten
(kein Dezimalpunkt — der wirkt in der Sperrschrift zu luftig).
`coords.format_ra`/`format_dec` nehmen `high_precision=` als Parameter (Vorgabe aus
`config`). Die Montierungs-Rohwerte werden im `coords`-Parser unverändert korrekt
zerlegt (Trennzeichen `:`, `*`, `0xDF`, `'` sind alle egal).
## Helligkeitsregelung
Portiert aus dem alten MSP430-Programm (`DoDimmen_V`, Werte aus `main.h`). Ein
LDR-Rohwert (12-Bit-ADC, 04095: je heller die Umgebung, desto höher der Wert)
wird über einen gleitenden Mittelwert (8 Werte) geglättet und auf eine
Helligkeitsstufe mit Hysterese abgebildet:
| Stufe | Helligkeit | erreicht ab (hoch / runter) |
|------|-----------|------------------------------|
| 0 | 5 % | < 270 |
| 1 | 15 % | ≥ 330 / < 270 |
| 2 | 50 % | ≥ 2650 / < 2350 |
| 3 | 100 % | ≥ 3650 / < 3350 |
Schwellen-Mitten `300 / 2500 / 3500`, Hysterese `±30 / ±150 / ±150`. **300 und 2500
stammen aus dem alten Code**; die obere Grenze `HELL_MAX = 3500` für 100 % ist neu
(der alte Code hatte keine 4. Stufe) und in `brightness.py` anpassbar, sobald der
reale LDR-Wertebereich feststeht. Ein Helligkeitstelegramm wird nur bei
**Stufenwechsel** gesendet (`ESC H 2 <ppp>`), wie im Original.
Auf dem Mac kommt der LDR-Wert per Kommandozeile (`run_display.py --ldr <wert>`),
auf dem ESP32-C3 vom ADC an GPIO2 (`run_esp32.main(with_ldr=True)`).
**Die Kennlinie ist auf der Platine umgekehrt.** Der Teiler lautet
`3V3 —[R2 2k]— A0 —[LDR]— GND`; der LDR wird bei mehr Licht niederohmiger, die
Spannung an A0 sinkt also mit steigender Helligkeit — genau andersherum als beim
alten MSP430-Aufbau, auf den die Schwellen oben zugeschnitten sind. Der Rohwert
wird deshalb gespiegelt: `config.LDR_INVERTED = True`, angewendet in
`brightness.raw_to_ldr`. Wer die Hardware lieber umbaut (LDR nach 3V3, R2 nach
GND), setzt das Flag auf `False`.
Die Schwellen selbst sind noch die alten und **müssen am realen Aufbau
nachgemessen werden**, siehe Offene Punkte.
## Antwortleitung (RX)
Der Rückkanal ist verdrahtet und verifiziert (2026-07-16): Anzeige Pin 3 (TxD) →
MAX232-Empfängereingang (R_IN) → R_OUT → Adapter RxD. Die Anzeige quittiert jedes
Telegramm mit `02 80 81 80 30 03` (Fehlercode `0` = kein Fehler). `WANT_RESPONSE = True`
ist damit aktiv: Nach der Antwort darf sofort das nächste Telegramm folgen, der
Fehlercode wird ausgewertet. `resp_test.py` prüft den Rückweg (sendet mit Antwort-
Anforderung und zeigt die empfangenen Antworten).
Wichtig: `SerialTransport.write` leert vor jedem Senden den Empfangspuffer, damit
`read_response` nur die frische Antwort sieht (sonst können Störbytes zwischen den
Telegrammen das Parsen stören). `run_display` fängt Anzeige-Fehler ab und zeichnet
beim nächsten Durchlauf neu, statt abzustürzen.
Diagnose-Historie: Der Rückweg war anfangs falsch verdrahtet — Pin 3 lag an einem
MAX232-*Sender*ausgang statt einem Empfängereingang; zwei Ausgänge trieben
gegeneinander (verschliffener ~3-V-Pegel am Oszi). `loopback.py` grenzt so etwas ein.
## Erledigt
- **RS232-Strecke ESP32 → MAX3232 → Anzeige bestätigt (2026-07-27).** Das war das
letzte ungetestete Kettenglied. Die Anzeige quittiert jedes Telegramm mit
Fehlercode 0, die volle Kette (Mock → WLAN → ESP32 → UART1 → MAX3232 → Anzeige)
läuft im Sekundentakt ohne Aussetzer. Damit ist die Portierung funktional
komplett; offen sind nur noch Betriebsthemen (LDR, Netzwerk, Autostart).
- **Phase 1 + 2 am Gerät bestätigt (2026-07-15/16):** Koordinaten erscheinen korrekt,
inkl. echtem Gradzeichen; im `run_display`-Betrieb aktualisieren beide Zeilen
laufend, sauber und ohne Flimmern, mit Antwort-Handshake der Anzeige. Protokoll,
19200 8E1, Zeichensatz und Zeilengeometrie stimmen.
- Schrift: Sperrschrift (`ESC z`, `CHARSET_SPACED = True`), **10 Zeichen/Zeile**
bei fester Positionierung auf 6 px (gemessen 27.07., siehe *Geometrie und
Darstellung*). Zwischenzeitlich stand hier 9 — das war am Umbruch abgezählt und
traf den Auto-Vorschub (7 px), nicht die Zeichenbreite.
Die schmale `1` wirkt etwas luftig — Font-Eigenschaft, nur per eigenem
Font (microSYST-PC-Software) änderbar.