140 lines
5.5 KiB
Python
140 lines
5.5 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.
|
|
|
|
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)
|