Nur geaenderte Zellen schreiben -- gegen das Flackern im Sekundentakt
Seit der Einzelpositionierung blitzten bei jeder Aktualisierung willkuerliche Pixel auf, in beiden Zeilen. Ursache ist die Telegrammlaenge: Eine volle Zeile kostet mit einzeln gesetzten Zeichen rund 100 Bytes statt 20, bei 19200 Baud und 8E1 (11 Bit je Byte) dauerte ein kompletter Bildwechsel damit rund 300 ms. Die Anzeige stellt schon waehrend des Empfangs dar, also war der Aufbau zu sehen -- im Sekundentakt ein Drittel der Zeit. Von einer Sekunde zur naechsten aendert sich aber fast nichts: "18h36m56s" -> "18h36m57s" ist eine einzige Ziffer, die Deklination steht meist still. show_lines vergleicht deshalb gegen den zuletzt dargestellten Inhalt und schreibt nur die betroffenen Zellen; eine unveraenderte Zeile erzeugt gar kein Telegramm mehr. Am Geraet gemessen: 306 ms -> 66 ms je Bildwechsel. Dabei am Geraet aufgefallen und mitbehoben: Dem "s" hinter den Sekunden fehlten die linken Pixel, dem "m" hinter den Minuten ebenso -- beides Zeichen direkt rechts von einer Stelle, die sich gerade geaendert hatte. Die Anzeige malt je Zeichen 7 px breit (daher auch der 7-px-Auto-Vorschub), gesetzt wird aber auf 6-px-Raster: Ein neu geschriebenes Zeichen loescht die erste Pixelspalte seines rechten Nachbarn. Beim vollstaendigen Neuaufbau fiel das nie auf, weil der Nachbar gleich danach ohnehin neu gemalt wurde. _zelle_faellig zieht ihn jetzt mit. Weitere Details: - clear() verwirft den gemerkten Inhalt, sonst haelt der Vergleich Zellen faelschlich fuer vorhanden und die Anzeige bliebe teilweise leer. - Schlaegt ein Telegramm fehl, wird der gemerkte Inhalt der Zeile verworfen -- was angekommen ist, ist dann ungewiss. - Der Zeichensatz wird nur noch bei der ersten Ausgabe einer Zeile gesetzt. Das spart nicht nur Bytes: Jeder Wechsel beschaedigt die Home-Zelle. Findet doch einer statt (neues Gradzeichen), wird die oberste Zeile vollstaendig nachgezogen. Zu beiden Befunden gibt es Tests mit Gegenprobe (mit der alten Fassung schlagen sie fehl). 155 Tests gruen, am Geraet bestaetigt: ruhig, keine fehlenden Pixel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -321,11 +321,30 @@ die Reihenfolge der Telegramme statt der Reihenfolge innerhalb eines Telegramms.
|
|||||||
mitwandernder Offset könnte das nicht; dafür ist die Zentrierung nur auf eine
|
mitwandernder Offset könnte das nicht; dafür ist die Zentrierung nur auf eine
|
||||||
halbe Zelle (3 px) genau, wenn die Restbreite ungerade ist.
|
halbe Zelle (3 px) genau, wenn die Restbreite ungerade ist.
|
||||||
|
|
||||||
- **Zyklische Updates flimmerfrei:** `show_lines` löscht nicht bei jeder Aktualisierung,
|
- **Es wird nur geschrieben, was sich geändert hat.** `show_lines` vergleicht gegen
|
||||||
sondern überschreibt die Zeilen an Ort und Stelle (auf volle Breite aufgefüllt).
|
den zuletzt dargestellten Inhalt und schickt nur die betroffenen Zellen; eine
|
||||||
`clear=True` nur beim ersten Bild. Wichtig: Zeilen dürfen **nicht breiter** als
|
unveränderte Zeile erzeugt gar kein Telegramm. `clear=True` nur beim ersten Bild
|
||||||
`CHARS_PER_LINE` aufgefüllt werden, sonst bricht das Füll-Leerzeichen um und
|
und nach einem Verbindungsabriss.
|
||||||
beschädigt die andere Zeile.
|
|
||||||
|
Das ist nicht nur Sparsamkeit, sondern der Grund für eine ruhige Anzeige. Mit
|
||||||
|
Einzelpositionierung kostet eine volle Zeile rund 100 Bytes statt 20; bei 19200
|
||||||
|
Baud mit 8E1 (11 Bit je Byte) dauerte ein kompletter Bildwechsel damit **rund
|
||||||
|
300 ms**. Die Anzeige stellt schon während des Empfangs dar, also war der Aufbau
|
||||||
|
als Flackern zu sehen — bei Aktualisierung im Sekundentakt ein Drittel der Zeit.
|
||||||
|
Von einer Sekunde zur nächsten ändert sich aber fast nichts: `18h36m56s` →
|
||||||
|
`18h36m57s` ist eine einzige Ziffer. Am Gerät gemessen (2026-07-27):
|
||||||
|
**306 ms → 66 ms** pro Bildwechsel.
|
||||||
|
|
||||||
|
- **Der rechte Nachbar wird mitgeschrieben.** Die Anzeige malt je Zeichen 7 px
|
||||||
|
breit (daher der 7-px-Auto-Vorschub), gesetzt wird aber auf 6-px-Raster. Ein neu
|
||||||
|
geschriebenes Zeichen löscht deshalb die erste Pixelspalte des Zeichens rechts
|
||||||
|
daneben. Beim vollständigen Neuaufbau fiel das nie auf, weil der Nachbar gleich
|
||||||
|
danach ohnehin neu gemalt wurde — beim Schreiben einzelner Zellen blieb er
|
||||||
|
beschädigt stehen: dem `s` hinter den Sekunden fehlten die linken Pixel, dem `m`
|
||||||
|
hinter den Minuten ebenso. `_zelle_faellig` zieht den rechten Nachbarn deshalb mit.
|
||||||
|
|
||||||
|
- Wichtig bleibt: Zeilen dürfen **nicht breiter** als `CHARS_PER_LINE` aufgefüllt
|
||||||
|
werden, sonst bricht das Füll-Leerzeichen um und beschädigt die andere Zeile.
|
||||||
|
|
||||||
## Präzision (Low / High)
|
## Präzision (Low / High)
|
||||||
|
|
||||||
|
|||||||
+121
-49
@@ -24,6 +24,9 @@ class Display:
|
|||||||
self._src = src
|
self._src = src
|
||||||
self._checksum = checksum
|
self._checksum = checksum
|
||||||
self._response = response
|
self._response = response
|
||||||
|
# Was gerade auf welcher Zeile steht. Grundlage fuer den Vergleich in
|
||||||
|
# show_lines: geschrieben wird nur, was sich geaendert hat.
|
||||||
|
self._gezeigt = {}
|
||||||
|
|
||||||
def send(self, data_unit):
|
def send(self, data_unit):
|
||||||
"""Data-Unit rahmen, senden und ggf. auf das Antworttelegramm warten."""
|
"""Data-Unit rahmen, senden und ggf. auf das Antworttelegramm warten."""
|
||||||
@@ -44,6 +47,10 @@ class Display:
|
|||||||
|
|
||||||
# -- Hoehere Funktionen ------------------------------------------------
|
# -- Hoehere Funktionen ------------------------------------------------
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
# Der gemerkte Inhalt gilt danach nicht mehr -- sonst hielte der
|
||||||
|
# Vergleich in show_lines Zellen faelschlich fuer schon vorhanden und
|
||||||
|
# die Anzeige bliebe nach dem Loeschen teilweise leer.
|
||||||
|
self._gezeigt = {}
|
||||||
self.send(migra.DataUnit().fill(config.BACKGROUND))
|
self.send(migra.DataUnit().fill(config.BACKGROUND))
|
||||||
|
|
||||||
def set_brightness(self, percent):
|
def set_brightness(self, percent):
|
||||||
@@ -52,82 +59,147 @@ class Display:
|
|||||||
def show_lines(self, *lines, clear=False):
|
def show_lines(self, *lines, clear=False):
|
||||||
"""Zeilen ab Zeile 1 auf die Anzeige schreiben.
|
"""Zeilen ab Zeile 1 auf die Anzeige schreiben.
|
||||||
|
|
||||||
Pro Zeile ein eigenes Telegramm: Cursor setzen und Text schreiben. Das
|
Geschrieben wird nur, was sich seit dem letzten Mal geaendert hat --
|
||||||
Gradzeichen (config.DEGREE_MARKER) wird ueber einen kurzen
|
Zelle fuer Zelle. Das ist der entscheidende Punkt fuer eine ruhige
|
||||||
Zeichensatzwechsel eingefuegt.
|
Anzeige:
|
||||||
|
|
||||||
Frueher war das ein einziges Telegramm fuer beide Zeilen. Seit jedes
|
Seit jedes Zeichen einzeln positioniert wird (10 statt 9 Zeichen, siehe
|
||||||
Zeichen einzeln positioniert wird (10 statt 9 Zeichen, siehe _emit_line),
|
_emit_line), kostet eine volle Zeile rund 100 Bytes statt 20. Bei 19200
|
||||||
kostet eine Zeile rund 100 Bytes -- beide zusammen kaemen mit Gradzeichen
|
Baud mit 8E1 (11 Bit je Byte) dauerte ein kompletter Bildwechsel damit
|
||||||
und Vollbild-Loeschen auf 224 der erlaubten 230 Bytes (migra.MAX_DATA_UNIT).
|
rund 300 ms. Die Anzeige stellt schon waehrend des Empfangs dar, also
|
||||||
Das ist zu wenig Reserve fuer ein Geraet, das monatelang durchlaeuft;
|
war der Aufbau als Flackern zu sehen -- bei Aktualisierung im
|
||||||
getrennte Telegramme halten jedes bei rund der Haelfte. Die Flusskontrolle
|
Sekundentakt ein Drittel der Zeit. (Am Geraet gemessen 2026-07-27.)
|
||||||
ueber das Antworttelegramm macht die zweite Sendung unproblematisch.
|
|
||||||
|
|
||||||
Jede Zeile wird mit Leerzeichen auf volle Breite (CHARS_PER_LINE)
|
Von einer Sekunde zur naechsten aendert sich aber fast nichts:
|
||||||
aufgefuellt und damit *an Ort und Stelle* ueberschrieben -- ohne die
|
"18h36m56s" -> "18h36m57s" ist ein einziges Zeichen, die Deklination
|
||||||
Anzeige vorher komplett zu loeschen. Das vermeidet das sichtbare Blitzen
|
steht meist voellig still. Mit dem Vergleich gegen den zuletzt
|
||||||
bei zyklischen Updates und haelt das Telegramm kurz (sonst kommt das
|
dargestellten Inhalt schrumpft der Bildwechsel auf ein bis zwei Zellen,
|
||||||
naechste zu frueh und das letzte Zeichen kann verloren gehen).
|
und eine unveraenderte Zeile erzeugt gar kein Telegramm mehr.
|
||||||
clear=True erzwingt zusaetzlich ein Vollbild-Loeschen (einmal beim Start).
|
|
||||||
|
|
||||||
Reihenfolge: Die Zeilen werden von unten nach oben gezeichnet, damit die
|
clear=True loescht das Vollbild und schreibt beide Zeilen komplett neu
|
||||||
oberste Zeile zuletzt kommt. Grund: Ein Zeichensatzwechsel mitten im
|
(einmal beim Start und nach jedem Verbindungsabriss).
|
||||||
Telegramm beschaedigt die Home-Zelle (0,0) -- die oberste Zeile muss also
|
|
||||||
nach allen Wechseln geschrieben werden, damit sie diese Zelle ueberschreibt.
|
Reihenfolge: Die Zeilen werden von unten nach oben geschrieben, damit die
|
||||||
(Am Geraet verifiziert 2026-07-15.)
|
oberste zuletzt kommt. Grund: Ein Zeichensatzwechsel beschaedigt die
|
||||||
|
Home-Zelle (0,0), also den Anfang der obersten Zeile. Findet ein solcher
|
||||||
|
Wechsel statt (Gradzeichen), wird die oberste Zeile deshalb vollstaendig
|
||||||
|
nachgezogen -- siehe _zeile_schreiben. (Am Geraet verifiziert 2026-07-15.)
|
||||||
"""
|
"""
|
||||||
if len(lines) > len(config.LINE_Y):
|
if len(lines) > len(config.LINE_Y):
|
||||||
raise ValueError("Anzeige hat nur %d Zeilen" % len(config.LINE_Y))
|
raise ValueError("Anzeige hat nur %d Zeilen" % len(config.LINE_Y))
|
||||||
|
|
||||||
first = True
|
if clear:
|
||||||
|
self.clear()
|
||||||
|
self._gezeigt = {}
|
||||||
|
|
||||||
|
wechsel = False
|
||||||
for i in reversed(range(len(lines))):
|
for i in reversed(range(len(lines))):
|
||||||
du = migra.DataUnit()
|
# Die oberste Zeile (i == 0) komplett neu, wenn weiter unten ein
|
||||||
if clear and first:
|
# Zeichensatzwechsel war: der hat ihren Anfang zerstoert.
|
||||||
du.fill(config.BACKGROUND)
|
voll = wechsel and i == 0
|
||||||
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
|
if self._zeile_schreiben(i, lines[i], voll=voll):
|
||||||
self._emit_line(du, config.LINE_Y[i], lines[i])
|
wechsel = True
|
||||||
self.send(du)
|
|
||||||
first = False
|
|
||||||
|
|
||||||
def show_line(self, index, text, clear=False):
|
def show_line(self, index, text, clear=False):
|
||||||
"""Nur eine einzelne Zeile aktualisieren (eigenes, kurzes Telegramm).
|
"""Nur eine einzelne Zeile aktualisieren.
|
||||||
|
|
||||||
Fuer zyklische Updates: die Zeile ohne Gradzeichen (also ohne
|
Schreibt ebenfalls nur die geaenderten Zellen. Achtung: Enthaelt die
|
||||||
Zeichensatzwechsel) laesst sich beliebig oft neu schreiben, ohne dass die
|
Zeile ein *neues* Gradzeichen, wird dafuer der Zeichensatz gewechselt und
|
||||||
Anzeige Bytes verschluckt. Zeilen *mit* Gradzeichen sollten dagegen selten
|
damit die Home-Zelle beschaedigt -- die oberste Zeile muesste danach
|
||||||
und moeglichst per show_lines (Vollbild) gezeichnet werden.
|
nachgezogen werden. Wer beide Zeilen hat, nimmt darum show_lines.
|
||||||
"""
|
"""
|
||||||
if not 0 <= index < len(config.LINE_Y):
|
if not 0 <= index < len(config.LINE_Y):
|
||||||
raise ValueError("ungueltiger Zeilenindex %d" % index)
|
raise ValueError("ungueltiger Zeilenindex %d" % index)
|
||||||
du = migra.DataUnit()
|
|
||||||
if clear:
|
if clear:
|
||||||
du.fill(config.BACKGROUND)
|
self.clear()
|
||||||
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
|
self._gezeigt = {}
|
||||||
self._emit_line(du, config.LINE_Y[index], text)
|
self._zeile_schreiben(index, text)
|
||||||
self.send(du)
|
|
||||||
|
|
||||||
def _emit_line(self, du, y, text):
|
def _aufbereiten(self, text):
|
||||||
"""Eine Zeile bei Pixelzeile y ausgeben, auf volle Breite aufgefuellt.
|
"""Auf Zeilenbreite bringen: abschneiden und auffuellen (ggf. mittig)."""
|
||||||
|
|
||||||
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).
|
# Ohne str.ljust/center: die kennt MicroPython nicht (am Geraet aufgefallen).
|
||||||
text = text[:config.CHARS_PER_LINE]
|
text = text[:config.CHARS_PER_LINE]
|
||||||
rest = config.CHARS_PER_LINE - len(text)
|
rest = config.CHARS_PER_LINE - len(text)
|
||||||
links = rest // 2 if config.CENTER_LINES else 0
|
links = rest // 2 if config.CENTER_LINES else 0
|
||||||
text = " " * links + text + " " * (rest - links)
|
return " " * links + text + " " * (rest - links)
|
||||||
|
|
||||||
|
def _zeile_schreiben(self, index, text, voll=False):
|
||||||
|
"""Eine Zeile nachfuehren. Liefert True, wenn der Zeichensatz gewechselt wurde.
|
||||||
|
|
||||||
|
voll=True schreibt alle Zellen, auch die unveraenderten.
|
||||||
|
"""
|
||||||
|
neu = self._aufbereiten(text)
|
||||||
|
alt = None if voll else self._gezeigt.get(index)
|
||||||
|
if alt == neu:
|
||||||
|
return False # nichts zu tun, kein Telegramm
|
||||||
|
|
||||||
|
du = migra.DataUnit()
|
||||||
|
du.attributes(fg=config.COLOR, bg=config.BACKGROUND, blink=migra.STATIC)
|
||||||
|
if alt is None:
|
||||||
|
# Erste Ausgabe dieser Zeile: Zeichensatz sicherheitshalber setzen.
|
||||||
|
# Danach behaelt die Anzeige ihn, und _emit_degree schaltet nach
|
||||||
|
# jedem Gradzeichen wieder zurueck -- ein Wechsel je Telegramm waere
|
||||||
|
# nur unnoetiger Ballast und wuerde die Home-Zelle beschaedigen.
|
||||||
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
|
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
|
||||||
|
wechsel = self._emit_line(du, config.LINE_Y[index], neu, alt)
|
||||||
|
try:
|
||||||
|
self.send(du)
|
||||||
|
except Exception:
|
||||||
|
# Ungewiss, wie viel die Anzeige bekommen hat. Den gemerkten Inhalt
|
||||||
|
# verwerfen, damit die Zeile beim naechsten Mal vollstaendig neu
|
||||||
|
# geschrieben wird -- sonst blieben Zellen stehen, die wir
|
||||||
|
# faelschlich fuer aktuell hielten.
|
||||||
|
self._gezeigt.pop(index, None)
|
||||||
|
raise
|
||||||
|
self._gezeigt[index] = neu
|
||||||
|
return wechsel or alt is None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _zelle_faellig(neu, alt, i):
|
||||||
|
"""Muss Zelle i neu geschrieben werden?
|
||||||
|
|
||||||
|
Ja, wenn sie sich selbst geaendert hat -- oder wenn ihr **linker**
|
||||||
|
Nachbar neu geschrieben wird: Der reicht 1 px in diese Zelle hinein und
|
||||||
|
loescht deren erste Pixelspalte (siehe _emit_line).
|
||||||
|
"""
|
||||||
|
def anders(k):
|
||||||
|
return k >= len(alt) or alt[k] != neu[k]
|
||||||
|
|
||||||
|
return anders(i) or (i > 0 and anders(i - 1))
|
||||||
|
|
||||||
|
def _emit_line(self, du, y, text, alt=None):
|
||||||
|
"""Zellen einer Zeile ausgeben. Liefert True bei Zeichensatzwechsel.
|
||||||
|
|
||||||
|
Jedes Zeichen wird einzeln auf x = LINE_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).
|
||||||
|
|
||||||
|
alt = zuletzt dargestellter Inhalt derselben Zeile. Ist er gegeben,
|
||||||
|
werden nur die Zellen geschrieben, die sich unterscheiden -- das haelt
|
||||||
|
den Bildwechsel kurz und die Anzeige ruhig (siehe show_lines).
|
||||||
|
|
||||||
|
**Der rechte Nachbar muss dabei mit.** Die Anzeige malt je Zeichen 7 px
|
||||||
|
breit (daher auch der 7-px-Auto-Vorschub), wir setzen die Zeichen aber
|
||||||
|
auf 6-px-Raster. Ein neu geschriebenes Zeichen loescht damit die erste
|
||||||
|
Pixelspalte des Zeichens rechts daneben. Beim vollstaendigen Neuaufbau
|
||||||
|
fiel das nie auf, weil der Nachbar gleich danach ohnehin neu gemalt
|
||||||
|
wurde; beim Schreiben einzelner Zellen bliebe er beschaedigt stehen.
|
||||||
|
Am Geraet aufgefallen (2026-07-27): Beim Sekundenwechsel fehlten dem "s"
|
||||||
|
die linken Pixel, beim Minutenwechsel dem "m".
|
||||||
|
"""
|
||||||
|
wechsel = False
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
|
if alt is not None and not self._zelle_faellig(text, alt, i):
|
||||||
|
continue
|
||||||
du.cursor(config.LINE_X + i * config.CELL_WIDTH, y)
|
du.cursor(config.LINE_X + i * config.CELL_WIDTH, y)
|
||||||
if text[i] == config.DEGREE_MARKER:
|
if text[i] == config.DEGREE_MARKER:
|
||||||
self._emit_degree(du)
|
self._emit_degree(du)
|
||||||
|
wechsel = True
|
||||||
else:
|
else:
|
||||||
du.text(text[i])
|
du.text(text[i])
|
||||||
|
return wechsel
|
||||||
|
|
||||||
def _emit_degree(self, du):
|
def _emit_degree(self, du):
|
||||||
"""Das Gradzeichen einfuegen -- ueber Zeichensatzwechsel oder als Fallback."""
|
"""Das Gradzeichen einfuegen -- ueber Zeichensatzwechsel oder als Fallback."""
|
||||||
|
|||||||
+155
@@ -81,6 +81,161 @@ class TestFesteTexte(unittest.TestCase):
|
|||||||
self.assertLessEqual(len(getattr(config, name)), len(config.LINE_Y))
|
self.assertLessEqual(len(getattr(config, name)), len(config.LINE_Y))
|
||||||
|
|
||||||
|
|
||||||
|
class TestNurGeaenderteZellen(unittest.TestCase):
|
||||||
|
"""Der Grund fuer den Vergleich: eine ruhige Anzeige.
|
||||||
|
|
||||||
|
Mit Einzelpositionierung kostet eine volle Zeile rund 100 Bytes; bei 19200
|
||||||
|
Baud dauerte ein kompletter Bildwechsel damit rund 300 ms, und der Aufbau
|
||||||
|
war als Flackern zu sehen (am Geraet gemessen 2026-07-27). Deshalb wird nur
|
||||||
|
geschrieben, was sich geaendert hat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.t = FakeTransport(echo=False)
|
||||||
|
self.d = Display(self.t, response=True)
|
||||||
|
|
||||||
|
def _neu(self):
|
||||||
|
"""Telegramme seit dem letzten Aufruf."""
|
||||||
|
frames = self.t.frames[:]
|
||||||
|
self.t.frames.clear()
|
||||||
|
return frames
|
||||||
|
|
||||||
|
def test_sekundenwechsel_schreibt_nur_die_noetigen_zellen(self):
|
||||||
|
"""Der Alltagsfall: RA tickt, alles andere steht still.
|
||||||
|
|
||||||
|
Geschrieben werden die geaenderte Ziffer *und* ihr rechter Nachbar --
|
||||||
|
siehe test_rechter_nachbar_wird_mitgeschrieben.
|
||||||
|
"""
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self._neu()
|
||||||
|
|
||||||
|
self.d.show_lines("18h36m57s", "+38°47'01\"")
|
||||||
|
frames = self._neu()
|
||||||
|
|
||||||
|
# Die Deklination ist unveraendert -- dafuer darf gar nichts gesendet werden.
|
||||||
|
self.assertEqual(len(frames), 1)
|
||||||
|
x = lambda i: config.LINE_X + i * config.CELL_WIDTH
|
||||||
|
self.assertEqual(zellen(frames), {(x(7), config.LINE_Y[0]): "7",
|
||||||
|
(x(8), config.LINE_Y[0]): "s"})
|
||||||
|
|
||||||
|
def test_rechter_nachbar_wird_mitgeschrieben(self):
|
||||||
|
"""Am Geraet aufgefallen (2026-07-27): dem "s" fehlten die linken Pixel.
|
||||||
|
|
||||||
|
Die Anzeige malt je Zeichen 7 px breit, gesetzt wird auf 6-px-Raster --
|
||||||
|
ein neu geschriebenes Zeichen loescht die erste Pixelspalte seines
|
||||||
|
rechten Nachbarn. Der muss deshalb mit, sonst bleibt er beschaedigt.
|
||||||
|
"""
|
||||||
|
self.d.show_lines("AAAAAAAAAA")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("AAAAXAAAAA") # nur Zelle 4 aendert sich
|
||||||
|
gesetzt = zellen(self._neu())
|
||||||
|
x = lambda i: config.LINE_X + i * config.CELL_WIDTH
|
||||||
|
y = config.LINE_Y[0]
|
||||||
|
self.assertIn((x(4), y), gesetzt) # die geaenderte Zelle
|
||||||
|
self.assertIn((x(5), y), gesetzt) # ihr rechter Nachbar
|
||||||
|
self.assertNotIn((x(3), y), gesetzt) # links davon bleibt unberuehrt
|
||||||
|
self.assertNotIn((x(6), y), gesetzt) # und uebernaechste auch
|
||||||
|
|
||||||
|
def test_letzte_zelle_hat_keinen_nachbarn(self):
|
||||||
|
"""Aendert sich die aeusserste rechte Zelle, gibt es nichts nachzuziehen."""
|
||||||
|
self.d.show_lines("AAAAAAAAAA")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("AAAAAAAAAX")
|
||||||
|
gesetzt = zellen(self._neu())
|
||||||
|
x = lambda i: config.LINE_X + i * config.CELL_WIDTH
|
||||||
|
self.assertEqual(list(gesetzt), [(x(9), config.LINE_Y[0])])
|
||||||
|
|
||||||
|
def test_unveraenderte_zeilen_erzeugen_kein_telegramm(self):
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self.assertEqual(self._neu(), [])
|
||||||
|
|
||||||
|
def test_telegramm_wird_deutlich_kuerzer(self):
|
||||||
|
"""Vorher-nachher in Bytes -- das ist die eigentliche Wirkung."""
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
voll = sum(len(f) for f in self._neu())
|
||||||
|
|
||||||
|
self.d.show_lines("18h36m57s", "+38°47'01\"")
|
||||||
|
diff = sum(len(f) for f in self._neu())
|
||||||
|
|
||||||
|
# Gemessen: 236 Bytes gegen 30 (Faktor knapp 8). Die Schranke liegt
|
||||||
|
# bewusst darunter, damit der Test nicht bei jeder Kleinigkeit kippt --
|
||||||
|
# er soll die Groessenordnung sichern, nicht eine exakte Zahl.
|
||||||
|
self.assertLess(diff * 5, voll,
|
||||||
|
"Der Sekundenwechsel sollte um ein Vielfaches "
|
||||||
|
"kuerzer sein als der Vollaufbau (%d vs %d Bytes)"
|
||||||
|
% (diff, voll))
|
||||||
|
|
||||||
|
def test_clear_erzwingt_vollstaendiges_neuschreiben(self):
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"", clear=True)
|
||||||
|
frames = self._neu()
|
||||||
|
# Loeschtelegramm + beide Zeilen vollstaendig
|
||||||
|
self.assertEqual(len(frames), 3)
|
||||||
|
self.assertEqual(len(zeile(frames, 0)), config.CHARS_PER_LINE)
|
||||||
|
self.assertEqual(len(zeile(frames, 1)), config.CHARS_PER_LINE)
|
||||||
|
|
||||||
|
def test_nach_clear_kein_rest_der_alten_anzeige(self):
|
||||||
|
"""clear muss den gemerkten Inhalt verwerfen.
|
||||||
|
|
||||||
|
Sonst haelt der Vergleich Zellen faelschlich fuer schon vorhanden und
|
||||||
|
die Anzeige bliebe nach dem Loeschen teilweise leer.
|
||||||
|
"""
|
||||||
|
self.d.show_lines("ABCDEFGHIJ", "0123456789")
|
||||||
|
self._neu()
|
||||||
|
self.d.clear()
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("ABCDEFGHIJ", "0123456789")
|
||||||
|
frames = self._neu()
|
||||||
|
self.assertEqual(zeile(frames, 0), "ABCDEFGHIJ")
|
||||||
|
self.assertEqual(zeile(frames, 1), "0123456789")
|
||||||
|
|
||||||
|
def test_nach_sendefehler_wird_die_zeile_voll_neu_geschrieben(self):
|
||||||
|
"""Bei einem Fehler ist ungewiss, was ankam -- also nichts annehmen."""
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self._neu()
|
||||||
|
|
||||||
|
class Kaputt(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def platzt(_):
|
||||||
|
raise Kaputt()
|
||||||
|
|
||||||
|
echt, self.d._t.write = self.d._t.write, platzt
|
||||||
|
with self.assertRaises(Kaputt):
|
||||||
|
self.d.show_lines("18h36m57s", "+38°47'01\"")
|
||||||
|
self.d._t.write = echt
|
||||||
|
self._neu()
|
||||||
|
|
||||||
|
# Dieselbe Zeile noch einmal: muss vollstaendig geschrieben werden.
|
||||||
|
self.d.show_lines("18h36m57s", "+38°47'01\"")
|
||||||
|
self.assertEqual(len(zeile(self._neu(), 0)), config.CHARS_PER_LINE)
|
||||||
|
|
||||||
|
def test_gradzeichen_wird_nicht_staendig_neu_gesetzt(self):
|
||||||
|
"""Spart nicht nur Bytes: Jeder Zeichensatzwechsel beschaedigt (0,0)."""
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("18h36m57s", "+38°47'02\"")
|
||||||
|
gesendet = b"".join(self._neu())
|
||||||
|
self.assertNotIn(b"\x1bz01", gesendet)
|
||||||
|
|
||||||
|
def test_neues_gradzeichen_zieht_die_oberste_zeile_nach(self):
|
||||||
|
"""Wird der Zeichensatz doch gewechselt, ist die Home-Zelle hin.
|
||||||
|
|
||||||
|
Dann muss die oberste Zeile vollstaendig nachgezogen werden, sonst
|
||||||
|
bleibt ihr erstes Zeichen zerstoert.
|
||||||
|
"""
|
||||||
|
self.d.show_lines("18h36m56s", "kein Grad")
|
||||||
|
self._neu()
|
||||||
|
self.d.show_lines("18h36m56s", "+38°47'01\"") # Gradzeichen kommt neu dazu
|
||||||
|
frames = self._neu()
|
||||||
|
self.assertIn(b"\x1bz01", b"".join(frames)) # Wechsel fand statt
|
||||||
|
# Obere Zeile trotz unveraendertem Text komplett neu geschrieben.
|
||||||
|
self.assertEqual(len(zeile(frames, 0)), config.CHARS_PER_LINE)
|
||||||
|
|
||||||
|
|
||||||
class TestZeilenaufbau(unittest.TestCase):
|
class TestZeilenaufbau(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
+5
-3
@@ -157,14 +157,16 @@ class TestDisplay(unittest.TestCase):
|
|||||||
t = FakeTransport(echo=False)
|
t = FakeTransport(echo=False)
|
||||||
Display(t, response=True).show_lines("12:34:56", "+45:12:30", clear=True)
|
Display(t, response=True).show_lines("12:34:56", "+45:12:30", clear=True)
|
||||||
|
|
||||||
self.assertEqual(len(t.frames), 2)
|
# clear=True ist ein eigenes, sehr kurzes Telegramm, danach je Zeile eins.
|
||||||
|
self.assertEqual(len(t.frames), 3)
|
||||||
for frame in t.frames:
|
for frame in t.frames:
|
||||||
self.assertEqual(frame[:4], bytes([0x02, 0x81, 0x80, 0x81]))
|
self.assertEqual(frame[:4], bytes([0x02, 0x81, 0x80, 0x81]))
|
||||||
self.assertEqual(frame[-1], 0x03)
|
self.assertEqual(frame[-1], 0x03)
|
||||||
self.assertLessEqual(len(frame), migra.MAX_DATA_UNIT)
|
self.assertLessEqual(len(frame), migra.MAX_DATA_UNIT)
|
||||||
|
|
||||||
unten, oben = t.frames # untere Zeile zuerst
|
loeschen, unten, oben = t.frames # untere Zeile zuerst
|
||||||
self.assertIn(b"\x1bF0", unten) # mit clear=True: einmal loeschen
|
self.assertIn(b"\x1bF0", loeschen)
|
||||||
|
self.assertNotIn(b"\x1bF0", unten)
|
||||||
self.assertNotIn(b"\x1bF0", oben)
|
self.assertNotIn(b"\x1bF0", oben)
|
||||||
|
|
||||||
# Jedes Zeichen sitzt auf seiner eigenen Zelle: x = LINE_X + i*CELL_WIDTH.
|
# Jedes Zeichen sitzt auf seiner eigenen Zelle: x = LINE_X + i*CELL_WIDTH.
|
||||||
|
|||||||
Reference in New Issue
Block a user