Files
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

150 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Geometrie der Anzeige ausmessen.
LINE_Y und CHARS_PER_LINE in config.py sind aus dem alten C-Programm abgeleitet,
nicht aus einem Datenblatt. Dieses Skript hilft, die echten Werte zu finden.
python3 probe.py ruler Ziffernlineal 12345678901234... -> Zeichen zaehlen
python3 probe.py pitch 6 Zellenbreite messen: Auto-Vorschub gegen 6 px
python3 probe.py pitch 7 dasselbe gegen 7 px
python3 probe.py lines Zeile 1 und Zeile 2 an den Positionen aus config.py
python3 probe.py scan-y dieselbe Zeile schrittweise nach unten schieben
python3 probe.py corners Punkte in die vier Ecken -> Pixelaufloesung pruefen
"""
import sys
import time
import config
import migra
from display import open_display
def ruler(d):
"""Ziffernlineal auf EINER Zeile: die Position, an der es umbricht, ist die Breite.
Bewusst nur eine Zeile und etwas mehr Zeichen als erwartet, damit man den
Umbruch sieht, ohne dass sich mehrere Zeilen ueberschreiben.
"""
n = config.CHARS_PER_LINE + 5
line = "".join(str((i + 1) % 10) for i in range(n))
du = migra.DataUnit()
du.fill(config.BACKGROUND)
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
du.attributes(fg=config.COLOR, bg=config.BACKGROUND)
du.cursor(0, config.LINE_Y[0])
du.text(line)
d.send(du)
print("Bis zu welcher Ziffer laeuft die obere Zeile, bevor sie umbricht?")
print("Das ist CHARS_PER_LINE (aktuell in config.py: %d)." % config.CHARS_PER_LINE)
def pitch(d, cell=6):
"""Zellenbreite direkt messen, statt Zeichen zu zaehlen.
Der Cursor arbeitet auf *Pixel*positionen -- das laesst sich ausnutzen:
Zeile 1: zehn Ziffern als ein Text ab x=0. Die Anzeige rueckt selbst vor,
mit der echten Zellenbreite.
Zeile 2: dieselben Ziffern einzeln auf x = i*cell gesetzt.
Stehen beide Zeilen exakt untereinander, ist `cell` die Zellenbreite. Ein
Unterschied von nur 1 px summiert sich ueber zehn Zeichen auf 9 px auf und
ist nicht zu uebersehen -- viel eindeutiger als das Abzaehlen am Umbruch.
5x7-Matrix mit 1 px Luecke waere cell=6 (dann passen 10 Zeichen in 64 px),
Sperrschrift mit 2 px Luecke waere cell=7 (dann 9 Zeichen).
"""
ziffern = "0123456789"
du = migra.DataUnit()
du.fill(config.BACKGROUND)
du.attributes(fg=config.COLOR, bg=config.BACKGROUND)
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
# Untere Zeile zuerst -- der Zeichensatzwechsel wuerde sonst die Home-Zelle
# der oberen Zeile beschaedigen (siehe display.show_lines).
for i, ch in enumerate(ziffern):
x = i * cell
if x + cell > 64:
break
du.cursor(x, config.LINE_Y[1])
du.text(ch)
du.cursor(0, config.LINE_Y[0])
du.text(ziffern)
d.send(du)
print("Obere Zeile: Anzeige rueckt selbst vor.")
print("Untere Zeile: Ziffern fest auf x = i*%d gesetzt." % cell)
print("")
print("Stehen sie exakt untereinander? -> Zellenbreite ist %d px," % cell)
print("also CHARS_PER_LINE = %d bei 64 px Breite." % (64 // cell))
print("Laeuft die obere Zeile nach rechts weg? -> Zelle ist breiter als %d px." % cell)
print("Dann mit einem groesseren Wert wiederholen: probe.py pitch %d" % (cell + 1))
def lines(d):
d.show_lines("Zeile 1", "Zeile 2")
print("Stehen beide Zeilen sauber untereinander? Sonst LINE_Y anpassen.")
def scan_y(d):
"""Zeile schrittweise nach unten schieben, um die y-Position zu finden."""
for y in range(0, 33, 2):
du = migra.DataUnit()
du.fill(config.BACKGROUND)
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
du.attributes(fg=config.COLOR, bg=config.BACKGROUND)
du.cursor(0, y)
du.text("y=%d" % y)
try:
d.send(du)
except migra.MigraError as e:
print("y=%-3d -> %s" % (y, e))
continue
print("y=%-3d -> ok" % y)
time.sleep(1.5)
def corners(d):
"""Punkte in die Ecken setzen. Ein Fehlercode '4' heisst: ausserhalb der Anzeige."""
for x, y in ((0, 0), (63, 0), (0, 15), (63, 15), (127, 31)):
du = migra.DataUnit().point(x, y, color=config.COLOR)
try:
d.send(du)
print("Punkt %3d/%-3d -> ok" % (x, y))
except migra.MigraError as e:
print("Punkt %3d/%-3d -> %s" % (x, y, e))
MODES = {"ruler": ruler, "pitch": pitch, "lines": lines, "scan-y": scan_y,
"corners": corners}
def main():
args = [a for a in sys.argv[1:] if not a.startswith("-")]
if not args or args[0] not in MODES:
print(__doc__)
return 2
extra = []
if args[0] == "pitch" and len(args) > 1:
try:
extra = [int(args[1])]
except ValueError:
print("pitch braucht eine Pixelzahl, z.B.: probe.py pitch 6")
return 2
response = config.WANT_RESPONSE and "--no-response" not in sys.argv
d = open_display(response=response)
try:
MODES[args[0]](d, *extra)
finally:
d.close()
return 0
if __name__ == "__main__":
sys.exit(main())