RA in Zehntelminuten mit Beschriftung: "RA 18h36.9m" / "DE +38°47'"

Die GM4000 liefert im Auslieferzustand keine Sekunden, sondern Zehntelminuten.
Bisher wurde daraus eine Sekundenanzeige gerechnet -- die sprang in
6-Sekunden-Schritten, weil die Rohdaten sie gar nicht hergeben. Die Zehntelform
ist ehrlicher und kuerzer, und der gewonnene Platz traegt die Beschriftung:

    RA 18h36.9m
    DE +38°47'

Bei Hochpraezision entfaellt die Beschriftung, dort braucht die Deklination mit
+38°47'01" alle Zellen.

Der Mock konnte das gar nicht nachstellen -- er sendete immer das
Hochpraezisionsformat. Neu: "mount_mock.py --low" bildet den Auslieferzustand ab
(RA als HH:MM.T, DEC ohne Bogensekunden). Vorgabe bleibt hoch, damit die
bestehenden Tests mit ihren festen Erwartungen unveraendert durchlaufen.

Zur Darstellung, alles am Geraet ausprobiert:

- **Schmale Zeichen** (config.CELL_NARROW): Dezimalpunkt und der Abstand
  zwischen Beschriftung und Wert bekommen 3 statt 6 px -- in der Sperrschrift
  standen sie sonst sehr luftig. Fuer die Leerzeichen am Zeilenende gilt das
  nicht, die sollen den Rest der Zeile ueberschreiben.
- Damit entscheidet die **Pixelbreite** ueber das Abschneiden, nicht mehr die
  Zeichenzahl: "RA 18h36.9m" sind 11 Zeichen und trotzdem nur 60 px.
- **Linksbuendig ab LINE_X = 0**, der Schalter zum Zentrieren ist entfallen.
  Zentriert standen die Zeilen um wenige Pixel gegeneinander versetzt, weil die
  RA-Zeile mit ihrem schmalen Punkt kuerzer ist als die DEC-Zeile.

Ein Zwischenschritt, der wieder verworfen wurde und als Warnung im Code steht:
Ein Doppelpunkt als Trenner ("RA:18h36.9m") sah in Zeichensatz 0 zu wuchtig aus;
der aus Zeichensatz 1 ist schlanker, steht aber mitten in der obersten Zeile --
und jeder Zeichensatzwechsel zerstoert die Home-Zelle. Das erste Zeichen danach
noch einmal zu schreiben machte es schlimmer: Das nachgezogene Zeichen bemalt
GLYPH_WIDTH Pixel und frisst die linke Spalte seines rechten Nachbarn. Deshalb
steht in der obersten Zeile jetzt bewusst kein Sonderzeichen.

161 Tests gruen, am Geraet bestaetigt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 18:23:07 +02:00
parent dae7694a07
commit 433c53aabd
10 changed files with 468 additions and 140 deletions
+116 -31
View File
@@ -115,13 +115,51 @@ class Display:
self._gezeigt = {}
self._zeile_schreiben(index, text)
def _aufbereiten(self, text):
"""Auf Zeilenbreite bringen: abschneiden und auffuellen (ggf. mittig)."""
@staticmethod
def _breiten(text):
"""Breite jeder einzelnen Zelle in Pixeln.
Zeichen aus config.CELL_NARROW brauchen weniger Platz. Fuer Leerzeichen
gilt das aber nur *innerhalb* des Textes -- dort trennen sie Beschriftung
und Wert ("RA 18h36.9m"). Die Leerzeichen, mit denen die Zeile rechts
aufgefuellt wird, behalten die volle Breite: Sie sollen den Rest der
Zeile ueberschreiben, und je schmaler sie waeren, desto mehr Zellen
muesste man dafuer schreiben.
"""
ohne_fuellung = len(text.rstrip())
return [config.CELL_NARROW.get(ch, config.CELL_WIDTH)
if i < ohne_fuellung else config.CELL_WIDTH
for i, ch in enumerate(text)]
@staticmethod
def _breite(text):
"""Pixelbreite eines Textes."""
return sum(Display._breiten(text))
@staticmethod
def _aufbereiten(text):
"""Auf die Zeilenbreite bringen: abschneiden und auffuellen.
Massgeblich sind die **Pixel**, nicht die Zeichenzahl: Seit schmale
Zeichen weniger Platz brauchen (config.CELL_NARROW), passen mal 10 und
mal 11 Zeichen in dieselbe Zeile. Ein Text wird deshalb so lange
gekuerzt, bis er in DISPLAY_WIDTH passt, und nur so weit aufgefuellt, wie
eine weitere Zelle noch hineingeht. Ohne das ragte die letzte Zelle ueber
den Rand und die Anzeige quittierte mit Fehlercode 4.
"""
# Ohne str.ljust/center: die kennt MicroPython nicht (am Geraet aufgefallen).
platz = config.DISPLAY_WIDTH - config.LINE_X
text = text[:config.CHARS_PER_LINE]
rest = config.CHARS_PER_LINE - len(text)
links = rest // 2 if config.CENTER_LINES else 0
return " " * links + text + " " * (rest - links)
while text and Display._breite(text) > platz:
text = text[:-1]
# Beim Auffuellen bewusst mit der *vollen* Zellenbreite rechnen, obwohl
# das Leerzeichen schmal gesetzt wird: Die Anzeige bemalt je Zeichen
# GLYPH_WIDTH Pixel, ein Leerzeichen am rechten Rand reichte sonst
# darueber hinaus.
while (len(text) < config.CHARS_PER_LINE
and Display._breite(text) + config.CELL_WIDTH <= platz):
text += " "
return text
def _zeile_schreiben(self, index, text, voll=False):
"""Eine Zeile nachfuehren. Liefert True, wenn der Zeichensatz gewechselt wurde.
@@ -129,19 +167,27 @@ class Display:
voll=True schreibt alle Zellen, auch die unveraenderten.
"""
neu = self._aufbereiten(text)
alt = None if voll else self._gezeigt.get(index)
if alt == neu:
pos = self._positionen(neu)
alt, alt_pos = self._gezeigt.get(index, (None, None))
if voll:
alt = None
elif alt_pos != pos:
# Die Zeile hat eine andere Form als vorher (z.B. ein Dezimalpunkt
# kommt dazu, der weniger Platz braucht). Dann sitzen alle Zellen
# woanders und der Vergleich waere sinnlos -- vollstaendig neu.
alt = None
elif 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.
# Danach behaelt die Anzeige ihn, und _emit_fremd schaltet nach
# jedem Sonderzeichen wieder zurueck -- ein Wechsel je Telegramm
# waere nur Ballast und wuerde die Home-Zelle beschaedigen.
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
wechsel = self._emit_line(du, config.LINE_Y[index], neu, alt)
wechsel = self._emit_line(du, config.LINE_Y[index], neu, alt, pos)
try:
self.send(du)
except Exception:
@@ -151,23 +197,43 @@ class Display:
# faelschlich fuer aktuell hielten.
self._gezeigt.pop(index, None)
raise
self._gezeigt[index] = neu
self._gezeigt[index] = (neu, pos)
return wechsel or alt is None
@staticmethod
def _zelle_faellig(neu, alt, i):
def _positionen(text):
"""x-Position jeder Zelle einer Zeile.
Normalerweise ein festes Raster aus CELL_WIDTH. Zeichen aus
config.CELL_NARROW bekommen weniger Platz -- der Dezimalpunkt der
Zehntelminuten stuende sonst sehr luftig zwischen den Ziffern.
"""
breiten = Display._breiten(text)
x = config.LINE_X
aus = []
for b in breiten:
aus.append(x)
x += b
return aus
@staticmethod
def _zelle_faellig(neu, alt, i, pos):
"""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).
Nachbar neu geschrieben wird und bis in diese Zelle hineinragt: Die
Anzeige bemalt je Zeichen GLYPH_WIDTH Pixel, mehr als eine Zelle breit
ist, und loescht damit die erste Pixelspalte hier (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))
if anders(i):
return True
return (i > 0 and anders(i - 1)
and pos[i] < pos[i - 1] + config.GLYPH_WIDTH)
def _emit_line(self, du, y, text, alt=None):
def _emit_line(self, du, y, text, alt=None, pos=None):
"""Zellen einer Zeile ausgeben. Liefert True bei Zeichensatzwechsel.
Jedes Zeichen wird einzeln auf x = LINE_X + i*CELL_WIDTH gesetzt, statt
@@ -189,26 +255,45 @@ class Display:
Am Geraet aufgefallen (2026-07-27): Beim Sekundenwechsel fehlten dem "s"
die linken Pixel, beim Minutenwechsel dem "m".
"""
if pos is None:
pos = self._positionen(text)
wechsel = False
for i in range(len(text)):
if alt is not None and not self._zelle_faellig(text, alt, i):
if alt is not None and not self._zelle_faellig(text, alt, i, pos):
continue
du.cursor(config.LINE_X + i * config.CELL_WIDTH, y)
if text[i] == config.DEGREE_MARKER:
self._emit_degree(du)
du.cursor(pos[i], y)
if self._emit_zeichen(du, text[i]):
wechsel = True
else:
du.text(text[i])
return wechsel
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 _emit_zeichen(self, du, ch):
"""Ein Zeichen ausgeben. Liefert True, wenn der Zeichensatz wechseln musste.
Nur das Gradzeichen kommt aus einem anderen Zeichensatz -- in
Zeichensatz 0 gibt es keines.
Ein Zeichensatzwechsel beschaedigt die Home-Zelle (0,0), also den Anfang
der obersten Zeile. Solange alle Wechsel in der *unteren* Zeile
stattfinden, genuegt die Reihenfolge in show_lines (unten vor oben).
**Ein Wechsel mitten in der obersten Zeile laesst sich dagegen nicht
sauber reparieren.** Das erste Zeichen danach noch einmal zu schreiben,
wurde am Geraet ausprobiert (2026-07-27) und machte es schlimmer: Das
nachgezogene Zeichen bemalt GLYPH_WIDTH Pixel und frisst dabei die linke
Spalte seines rechten Nachbarn. Deshalb steht in der obersten Zeile
bewusst kein Sonderzeichen -- die Beschriftung nutzt ein schmales
Leerzeichen statt eines Doppelpunkts aus Zeichensatz 1.
"""
if ch == config.DEGREE_MARKER:
if config.DEGREE_CHARSET is None:
du.text(config.DEGREE_FALLBACK)
return False
du.charset(config.DEGREE_CHARSET, spaced=config.CHARSET_SPACED)
du.text(config.DEGREE_CHAR)
du.charset(config.CHARSET, spaced=config.CHARSET_SPACED)
return True
du.text(ch)
return False
def close(self):
self._t.close()