"""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)