feat(station): ausgelieferte Firmware-Version auf dem Panel

Welche Version die Station gerade ausliefert, war am Geraet nur per SSH zu
beantworten. Jetzt sagt es die Anzeige selbst, je nach Bild an der Stelle, wo
Platz ist:

- Aus-Zustand: rechtsbuendig neben der Adresse (neue Rolle ROLE_DETAIL_RIGHT,
  nach dem Muster der Phase neben dem Modus)
- Rettung: rechtsbuendig in der Kopfzeile, dort steht keine Phase im Weg
- Migration: als beschriftete Zeile "liefert: <version>" direkt hinter "FW:",
  NICHT in der Kopfzeile - daneben bleiben nur rund 112 px, daran ist schon
  "FREIGEGEBEN: <ziel>" gescheitert, und zwei nackte Versionsnummern
  nebeneinander waeren eine Verwechslungsfalle (FW: ist die der Lampe)

Quelle ist der GEWAEHLTE Bildsatz: im Migrationsmodus manifest.json (die
Migrationsabzuege tragen keinen ninlight-Anhaenger), im Rettungsmodus der
Anhaenger der _ota.img. Weichen die Eintraege voneinander ab, steht da
"gemischt" - ein zusammengefuehrter Deploy kann Alt-Eintraege mit anderer
Version enthalten, und eine davon herauszugreifen waere geraten. Fehlt die
Quelle, bleibt die Angabe weg statt eine Version zu erfinden.
This commit is contained in:
Michael Sauer
2026-10-02 23:44:37 +02:00
parent c53a23daba
commit 58cdc73afc
3 changed files with 190 additions and 4 deletions
+60 -2
View File
@@ -957,9 +957,11 @@ def test_build_state_off_when_service_not_active(tmp_path, monkeypatch):
last_mode_path=tmp_path / "last-mode")
# "off" und sonst NICHTS aus status.json - nur der Bildsatz, der auch im
# ausgeschalteten Zustand gilt (und gerade dann gesehen werden soll).
# ausgeschalteten Zustand gilt (und gerade dann gesehen werden soll), die Adresse
# fuer SSH und die Version, die die Station ausliefern WUERDE.
assert state["mode"] == "off"
assert set(state) == {"mode", "imageset", "imageset_ok", "ip", "iface"}
assert set(state) == {"mode", "imageset", "imageset_ok", "ip", "iface",
"image_version"}
assert isinstance(state["ip"], str)
@@ -2462,3 +2464,59 @@ def test_fixed_labels_fit_the_panel_with_room_for_a_wider_font(source, text, rol
"{}: {!r} misst {:.1f} px; mit {:.0f} % Reserve fuer eine breitere Schrift "
"bleiben nur {:.1f} px Platz".format(
source, text, width_px, (FOOTER_WIDTH_SAFETY - 1) * 100, max_width_px))
# --- Ausgelieferte Firmware-Version ---------------------------------------------------
#
# "Welche Version liefert die Station gerade aus?" war am Geraet nur zu beantworten,
# indem man sich per SSH die Abzuege ansah. Die Anzeige sagt es jetzt selbst.
def test_off_screen_shows_delivered_version_next_to_the_address():
bloecke = render.blocks({"mode": "off", "ip": "192.168.2.96", "iface": "wlan0",
"image_version": "0.4.0"})
assert ("wlan0: 192.168.2.96", render.ROLE_DETAIL) in bloecke
assert ("0.4.0", render.ROLE_DETAIL_RIGHT) in bloecke
# Rechts NEBEN der Adresse, also unmittelbar dahinter - sonst landete sie neben
# einer anderen Zeile.
assert bloecke.index(("0.4.0", render.ROLE_DETAIL_RIGHT)) == (
bloecke.index(("wlan0: 192.168.2.96", render.ROLE_DETAIL)) + 1
)
def test_rescue_shows_delivered_version_in_the_header():
bloecke = render.blocks({"mode": "recovery", "ssid": "ninlight-rescue",
"image_version": "0.4.0"})
assert ("0.4.0", render.ROLE_HEADER_PHASE) in bloecke
def test_migration_labels_the_delivered_version():
"""Im Migrationsmodus beschriftet, weil daneben schon die Lampenversion steht."""
bloecke = render.blocks({
"mode": "migration", "phase": "auto", "image_version": "0.4.0",
"last_seen": {"product_name": "Klyqa E27 RGB", "lamp_version": "1.1.1.2358"},
})
texte = [text for text, _ in bloecke]
assert "FW: 1.1.1.2358" in texte
assert "liefert: 0.4.0" in texte
# NICHT in die Kopfzeile: dort steht die Phase.
assert ("0.4.0", render.ROLE_HEADER_PHASE) not in bloecke
assert ("AUTO", render.ROLE_HEADER_PHASE) in bloecke
def test_unknown_version_shows_nothing():
"""Lieber keine Angabe als eine erfundene - sie beantwortet sonst genau die Frage
falsch, fuer die sie da ist."""
for zustand in ({"mode": "off", "ip": "192.168.2.96", "iface": "wlan0"},
{"mode": "off", "ip": "192.168.2.96", "iface": "wlan0",
"image_version": ""},
{"mode": "recovery", "ssid": "x", "image_version": None}):
bloecke = render.blocks(zustand)
assert not [b for b in bloecke if b[1] == render.ROLE_DETAIL_RIGHT]
assert not [b for b in bloecke if b[1] == render.ROLE_HEADER_PHASE]
def test_mixed_sets_are_called_out():
bloecke = render.blocks({"mode": "off", "ip": "10.0.0.5", "iface": "eth0",
"image_version": "gemischt"})
assert ("gemischt", render.ROLE_DETAIL_RIGHT) in bloecke