First commit

This commit is contained in:
2026-07-16 09:05:56 +02:00
commit 81e0e1a036
26 changed files with 1821 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
"""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.
Baut ein einziges Telegramm: pro Zeile Cursor setzen und den Text
schreiben. Das Gradzeichen (config.DEGREE_MARKER) wird ueber einen kurzen
Zeichensatzwechsel eingefuegt.
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))
du = migra.DataUnit()
if clear:
du.fill(config.BACKGROUND)
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
for i in reversed(range(len(lines))):
self._emit_line(du, config.LINE_Y[i], lines[i])
self.send(du)
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."""
text = text[:config.CHARS_PER_LINE].ljust(config.CHARS_PER_LINE)
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
du.cursor(0, y)
for i, part in enumerate(text.split(config.DEGREE_MARKER)):
if i > 0:
self._emit_degree(du)
if part:
du.text(part)
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=config.PORT, fake=False, response=config.WANT_RESPONSE):
"""Transport passend zur Plattform oeffnen und Display-Objekt liefern."""
if fake:
from transport import FakeTransport
t = FakeTransport(src=config.HOST_ADDR, display_addr=config.DISPLAY_ADDR)
else:
from transport import SerialTransport
t = SerialTransport(
port=port,
baudrate=config.BAUDRATE,
parity=config.PARITY,
bytesize=config.BYTESIZE,
stopbits=config.STOPBITS,
timeout=config.RESPONSE_TIMEOUT,
)
return Display(t, response=response)