feat(station): Abschlussbild beim Herunterfahren und beim Neustart
E-Paper behaelt sein Bild dauerhaft, auch stromlos. Faehrt der Pi herunter, wird der Anzeigedienst mit abgeraeumt und das zuletzt gezeichnete Bild bleibt fuer immer stehen - bei der Abnahme stand dort die Rueckfrage des Tastermenues, und der ausgeschaltete Pi sah aus, als warte er noch auf eine Eingabe. Zwei neue Units, ninlight-poweroff.service (WantedBy=poweroff.target) und ninlight-reboot.service (WantedBy=reboot.target). Sie haengen am System, nicht am Tasterdienst: so greifen sie bei jedem Herunterfahren - Menue, "poweroff" per SSH, "reboot" - und nicht nur auf einem von drei Wegen. Zwei Units statt einer Fallunterscheidung, weil systemd einem ExecStart nicht sagt, wohin die Reise geht. Die Texte unterscheiden sich: "AUSGESCHALTET / Strom kann getrennt werden" gegen "NEUSTART / Strom NICHT trennen!" samt Hinweis auf die rund zwei Minuten, die der Zero W bis zum Anzeigedienst braucht. An diesem Pi hat ein Stromzug in genau dieser Wartezeit schon eine SD-Karte gekostet. Reihenfolge: die Units ordnen sich per After=ninlight-display.service hinter dessen Stop ein, und "ninlight-station display final <kind>" wartet zusaetzlich, bis systemctl is-active nicht mehr anschlaegt - dasselbe Muster wie display_handover(), nur ohne Wiederanfahren. Gibt der Dienst die Leitungen nicht frei, wird nicht gezeichnet (GPIO busy), sondern gewarnt: ein Anzeigefehler haelt das Herunterfahren nie auf. Nebenbei behoben: display_run() reichte nur "$1" an display.py weiter und haette "--final poweroff" halbiert.
This commit is contained in:
@@ -1865,3 +1865,126 @@ def test_the_display_and_the_server_agree_on_the_set_names():
|
||||
spec.loader.exec_module(server)
|
||||
assert display.IMAGESET_DIRS == server.IMAGE_SET_DIRS
|
||||
assert display.IMAGESET_DEFAULT == server.DEFAULT_IMAGE_SET
|
||||
|
||||
|
||||
# --- Abschlussbildschirm beim Herunterfahren und beim Neustart -----------------------
|
||||
#
|
||||
# E-Paper ist bistabil: das zuletzt gezeichnete Bild bleibt fuer immer stehen, auch
|
||||
# stromlos. Ohne dieses Bild behauptet das Panel nach dem Ausschalten weiter den Zustand
|
||||
# von vorher - bei der Abnahme stand dort die Rueckfrage des Tastermenues, und der Pi sah
|
||||
# aus, als warte er noch auf eine Eingabe.
|
||||
|
||||
def _final_text(kind):
|
||||
return " ".join(render.lines({"mode": "final", "final": kind}))
|
||||
|
||||
|
||||
def test_the_shutdown_and_the_reboot_screen_differ():
|
||||
"""Der wichtigste Unterschied im ganzen Bild: beim Ausschalten DARF man den Stecker
|
||||
ziehen, beim Neustart NICHT (an diesem Pi hat ein Stromzug schon eine SD-Karte
|
||||
gekostet)."""
|
||||
off, reboot = _final_text("poweroff"), _final_text("reboot")
|
||||
assert off != reboot
|
||||
assert "AUSGESCHALTET" in off and "NEUSTART" in reboot
|
||||
# Ausschalten: erlaubt das Trennen ausdruecklich.
|
||||
assert "kann getrennt werden" in off
|
||||
# Neustart: verbietet es ebenso ausdruecklich - und sagt nirgends das Gegenteil.
|
||||
assert "NICHT trennen" in reboot
|
||||
assert "kann getrennt werden" not in reboot
|
||||
|
||||
|
||||
def test_the_reboot_screen_says_how_long_it_stays():
|
||||
"""Ein Zero W braucht gut anderthalb Minuten bis zum Anzeigedienst - ohne diesen Satz
|
||||
sieht das stehende Bild nach "haengt" aus, und genau dann greift jemand zum Stecker."""
|
||||
assert "min" in _final_text("reboot")
|
||||
|
||||
|
||||
def test_the_final_screen_carries_nothing_else():
|
||||
"""Kein Bildsatz, keine Fusszeile, kein Zaehler: die Station ist im Begriff zu
|
||||
verschwinden, und die einzige Auskunft, auf die es jetzt ankommt, ist "Stecker?"."""
|
||||
state = {"mode": "final", "final": "poweroff", "imageset": "mit-wlan",
|
||||
"imageset_ok": True, "served": 7}
|
||||
blocks = render.blocks(state)
|
||||
assert not any(role == render.ROLE_FOOTER for _, role in blocks)
|
||||
assert not any("Bildsatz" in text or "Ausgeliefert" in text for text, _ in blocks)
|
||||
|
||||
|
||||
def test_an_unknown_final_kind_claims_neither():
|
||||
"""Lieber gar nichts behaupten als "ausgeschaltet" zu sagen, wenn gleich wieder
|
||||
gebootet wird."""
|
||||
text = " ".join(render.lines({"mode": "final", "final": "was-auch-immer"}))
|
||||
assert "AUSGESCHALTET" not in text
|
||||
assert "getrennt" not in text
|
||||
|
||||
|
||||
def test_both_final_screens_are_actually_drawable():
|
||||
for kind in ("poweroff", "reboot"):
|
||||
image = render.compose({"mode": "final", "final": kind})
|
||||
assert image.size == (render.PANEL_WIDTH_PX, render.PANEL_HEIGHT_PX)
|
||||
assert _ink_box(image) is not None, kind
|
||||
|
||||
|
||||
def test_run_final_draws_fully_and_sleeps(monkeypatch):
|
||||
"""Ueber die ECHTE display.run_final(): Vollaufbau (kein Teilaufbau - dieses eine Bild
|
||||
ueberdauert alle anderen und darf keine Reste des vorigen tragen) und danach schlafen
|
||||
legen, damit das Panel nicht unter Bias in den Ruhezustand geht."""
|
||||
calls = []
|
||||
|
||||
class _Panel:
|
||||
def __init__(self, revision):
|
||||
calls.append(("init", revision))
|
||||
|
||||
def clear(self):
|
||||
calls.append(("clear", None))
|
||||
|
||||
def draw_full(self, image):
|
||||
calls.append(("full", image))
|
||||
|
||||
def draw_partial(self, image):
|
||||
calls.append(("partial", image))
|
||||
return False
|
||||
|
||||
def sleep(self):
|
||||
calls.append(("sleep", None))
|
||||
|
||||
monkeypatch.setattr(display, "Panel", _Panel)
|
||||
display.run_final("V3", "reboot")
|
||||
|
||||
kinds = [k for k, _ in calls]
|
||||
assert kinds == ["init", "clear", "full", "sleep"], kinds
|
||||
# ... und gezeichnet wurde wirklich das Neustart-Bild, nicht irgendeines.
|
||||
image = dict((k, v) for k, v in calls)["full"]
|
||||
expected = render.compose({"mode": "final", "final": "reboot"})
|
||||
assert list(image.getdata()) == list(expected.getdata())
|
||||
assert list(image.getdata()) != list(
|
||||
render.compose({"mode": "final", "final": "poweroff"}).getdata())
|
||||
|
||||
|
||||
def test_display_main_passes_the_final_kind_through(monkeypatch):
|
||||
"""Die Verdrahtung in display.main() selbst: "--final reboot" muss beim Neustartbild
|
||||
landen. Ohne diese Zusage liesse sich die Spielart in main() durch eine feste
|
||||
ersetzen, ohne dass ein Test rot wuerde - und auf dem Panel stuende beim Neustart
|
||||
"Strom kann getrennt werden"."""
|
||||
drawn = []
|
||||
|
||||
class _Panel:
|
||||
def __init__(self, revision):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
def draw_full(self, image):
|
||||
drawn.append(image)
|
||||
|
||||
def sleep(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(display, "Panel", _Panel)
|
||||
monkeypatch.setenv("EPD_REVISION", "V3")
|
||||
for kind in ("reboot", "poweroff"):
|
||||
drawn.clear()
|
||||
monkeypatch.setattr(sys, "argv", ["display.py", "--final", kind])
|
||||
display.main()
|
||||
assert len(drawn) == 1
|
||||
expected = render.compose({"mode": "final", "final": kind})
|
||||
assert list(drawn[0].getdata()) == list(expected.getdata()), kind
|
||||
|
||||
Reference in New Issue
Block a user