4f7375add3
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>
177 lines
7.4 KiB
Python
177 lines
7.4 KiB
Python
"""Treiber fuer die Grossanzeige: Telegramme bauen, senden, Antwort pruefen."""
|
|
|
|
import time
|
|
|
|
import migra
|
|
import config
|
|
|
|
|
|
class Display:
|
|
"""Sendet Telegramme an die Anzeige und wertet das Antworttelegramm aus.
|
|
|
|
Das Antworttelegramm ist zugleich die Flusskontrolle: Nach seinem Erhalt
|
|
darf sofort das naechste Telegramm folgen (Handbuch 3.5). Ohne Antwort
|
|
muesste zwischen den Telegrammen "Empfangstimeout + 150 ms" gewartet werden.
|
|
"""
|
|
|
|
def __init__(self, transport,
|
|
dest=config.DISPLAY_ADDR,
|
|
src=config.HOST_ADDR,
|
|
checksum=config.USE_CHECKSUM,
|
|
response=config.WANT_RESPONSE):
|
|
self._t = transport
|
|
self._dest = dest
|
|
self._src = src
|
|
self._checksum = checksum
|
|
self._response = response
|
|
|
|
def send(self, data_unit):
|
|
"""Data-Unit rahmen, senden und ggf. auf das Antworttelegramm warten."""
|
|
frame = migra.build_telegram(
|
|
data_unit,
|
|
dest=self._dest,
|
|
src=self._src,
|
|
checksum=self._checksum,
|
|
response=self._response,
|
|
)
|
|
self._t.write(frame)
|
|
if self._response:
|
|
migra.parse_response(self._t.read_response(), src=self._src)
|
|
else:
|
|
# Ohne Antwort ist die Anzeige erst nach Abarbeitung des Telegramms
|
|
# wieder empfangsbereit: Empfangstimeout + 150 ms (Handbuch 3.5).
|
|
time.sleep(config.QUIET_TIME)
|
|
|
|
# -- Hoehere Funktionen ------------------------------------------------
|
|
def clear(self):
|
|
self.send(migra.DataUnit().fill(config.BACKGROUND))
|
|
|
|
def set_brightness(self, percent):
|
|
self.send(migra.DataUnit().brightness(percent, color=config.COLOR))
|
|
|
|
def show_lines(self, *lines, clear=False):
|
|
"""Zeilen ab Zeile 1 auf die Anzeige schreiben.
|
|
|
|
Pro Zeile ein eigenes Telegramm: Cursor setzen und Text schreiben. Das
|
|
Gradzeichen (config.DEGREE_MARKER) wird ueber einen kurzen
|
|
Zeichensatzwechsel eingefuegt.
|
|
|
|
Frueher war das ein einziges Telegramm fuer beide Zeilen. Seit jedes
|
|
Zeichen einzeln positioniert wird (10 statt 9 Zeichen, siehe _emit_line),
|
|
kostet eine Zeile rund 100 Bytes -- beide zusammen kaemen mit Gradzeichen
|
|
und Vollbild-Loeschen auf 224 der erlaubten 230 Bytes (migra.MAX_DATA_UNIT).
|
|
Das ist zu wenig Reserve fuer ein Geraet, das monatelang durchlaeuft;
|
|
getrennte Telegramme halten jedes bei rund der Haelfte. Die Flusskontrolle
|
|
ueber das Antworttelegramm macht die zweite Sendung unproblematisch.
|
|
|
|
Jede Zeile wird mit Leerzeichen auf volle Breite (CHARS_PER_LINE)
|
|
aufgefuellt und damit *an Ort und Stelle* ueberschrieben -- ohne die
|
|
Anzeige vorher komplett zu loeschen. Das vermeidet das sichtbare Blitzen
|
|
bei zyklischen Updates und haelt das Telegramm kurz (sonst kommt das
|
|
naechste zu frueh und das letzte Zeichen kann verloren gehen).
|
|
clear=True erzwingt zusaetzlich ein Vollbild-Loeschen (einmal beim Start).
|
|
|
|
Reihenfolge: Die Zeilen werden von unten nach oben gezeichnet, damit die
|
|
oberste Zeile zuletzt kommt. Grund: Ein Zeichensatzwechsel mitten im
|
|
Telegramm beschaedigt die Home-Zelle (0,0) -- die oberste Zeile muss also
|
|
nach allen Wechseln geschrieben werden, damit sie diese Zelle ueberschreibt.
|
|
(Am Geraet verifiziert 2026-07-15.)
|
|
"""
|
|
if len(lines) > len(config.LINE_Y):
|
|
raise ValueError("Anzeige hat nur %d Zeilen" % len(config.LINE_Y))
|
|
|
|
first = True
|
|
for i in reversed(range(len(lines))):
|
|
du = migra.DataUnit()
|
|
if clear and first:
|
|
du.fill(config.BACKGROUND)
|
|
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
|
|
self._emit_line(du, config.LINE_Y[i], lines[i])
|
|
self.send(du)
|
|
first = False
|
|
|
|
def show_line(self, index, text, clear=False):
|
|
"""Nur eine einzelne Zeile aktualisieren (eigenes, kurzes Telegramm).
|
|
|
|
Fuer zyklische Updates: die Zeile ohne Gradzeichen (also ohne
|
|
Zeichensatzwechsel) laesst sich beliebig oft neu schreiben, ohne dass die
|
|
Anzeige Bytes verschluckt. Zeilen *mit* Gradzeichen sollten dagegen selten
|
|
und moeglichst per show_lines (Vollbild) gezeichnet werden.
|
|
"""
|
|
if not 0 <= index < len(config.LINE_Y):
|
|
raise ValueError("ungueltiger Zeilenindex %d" % index)
|
|
du = migra.DataUnit()
|
|
if clear:
|
|
du.fill(config.BACKGROUND)
|
|
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
|
|
self._emit_line(du, config.LINE_Y[index], text)
|
|
self.send(du)
|
|
|
|
def _emit_line(self, du, y, text):
|
|
"""Eine Zeile bei Pixelzeile y ausgeben, auf volle Breite aufgefuellt.
|
|
|
|
Jedes Zeichen wird einzeln auf x = i*CELL_WIDTH gesetzt, statt den
|
|
Auto-Vorschub der Anzeige laufen zu lassen. Der Vorschub der Sperrschrift
|
|
ist 7 px und liesse nur 9 Zeichen zu; die Zeichenmatrix ist aber nur 6 px
|
|
breit, sodass 10 Zeichen sauber nebeneinander passen (am Geraet gemessen
|
|
2026-07-27, siehe config.CHARS_PER_LINE).
|
|
"""
|
|
# Ohne str.ljust/center: die kennt MicroPython nicht (am Geraet aufgefallen).
|
|
text = text[:config.CHARS_PER_LINE]
|
|
rest = config.CHARS_PER_LINE - len(text)
|
|
links = rest // 2 if config.CENTER_LINES else 0
|
|
text = " " * links + text + " " * (rest - links)
|
|
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
|
|
for i in range(len(text)):
|
|
du.cursor(config.LINE_X + i * config.CELL_WIDTH, y)
|
|
if text[i] == config.DEGREE_MARKER:
|
|
self._emit_degree(du)
|
|
else:
|
|
du.text(text[i])
|
|
|
|
def _emit_degree(self, du):
|
|
"""Das Gradzeichen einfuegen -- ueber Zeichensatzwechsel oder als Fallback."""
|
|
if config.DEGREE_CHARSET is None:
|
|
du.text(config.DEGREE_FALLBACK)
|
|
return
|
|
du.charset(config.DEGREE_CHARSET, spaced=config.CHARSET_SPACED)
|
|
du.text(config.DEGREE_CHAR)
|
|
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
|
|
|
|
def close(self):
|
|
self._t.close()
|
|
|
|
|
|
def open_display(port=None, fake=False, response=config.WANT_RESPONSE):
|
|
"""Transport passend zur Plattform oeffnen und Display-Objekt liefern.
|
|
|
|
Auf dem ESP32-C3 wird machine.UART genommen (Pins aus config), auf dem Mac
|
|
pyserial mit config.PORT. fake=True haengt stattdessen den Mitschreiber ein.
|
|
"""
|
|
if fake:
|
|
from transport import FakeTransport
|
|
t = FakeTransport(src=config.HOST_ADDR, display_addr=config.DISPLAY_ADDR)
|
|
elif config.MICROPYTHON:
|
|
from transport import UartTransport
|
|
t = UartTransport(
|
|
uart_id=config.UART_ID,
|
|
baudrate=config.BAUDRATE,
|
|
parity=config.PARITY,
|
|
bytesize=config.BYTESIZE,
|
|
stopbits=config.STOPBITS,
|
|
timeout=config.RESPONSE_TIMEOUT,
|
|
tx=config.UART_TX,
|
|
rx=config.UART_RX,
|
|
)
|
|
else:
|
|
from transport import SerialTransport
|
|
t = SerialTransport(
|
|
port=config.PORT if port is None else port,
|
|
baudrate=config.BAUDRATE,
|
|
parity=config.PARITY,
|
|
bytesize=config.BYTESIZE,
|
|
stopbits=config.STOPBITS,
|
|
timeout=config.RESPONSE_TIMEOUT,
|
|
)
|
|
return Display(t, response=response)
|