diff --git a/device_server.py b/device_server.py index f5b6642..d59bed1 100644 --- a/device_server.py +++ b/device_server.py @@ -13,6 +13,7 @@ import re import socket import subprocess import threading +import time from flask import Flask, jsonify, request @@ -25,6 +26,7 @@ KIOSK_SCRIPT = "/root/display_test_nexio.py" _video_proc: subprocess.Popen | None = None _video_lock = threading.Lock() +_kiosk_args: list[str] = ["python3", KIOSK_SCRIPT] # updated when kiosk is started # --------------------------------------------------------------------------- # Register commands to execute on each GET /registers request. @@ -89,16 +91,38 @@ def _parse_memtool_output(raw: str) -> list: @app.route("/display", methods=["PUT"]) def control_display(): + global _video_proc data = request.get_json(force=True) or {} state = data.get("state", "").lower() if state == "on": with _video_lock: - if _video_proc is not None and _video_proc.poll() is None: + if "--static-pink" in _kiosk_args: + # Full kill+restart so the DSI controller goes through LP-11 → HS + # startup, giving the scope a clean LP trigger on Pass 1. + if _video_proc is not None and _video_proc.poll() is None: + _video_proc.terminate() + try: + _video_proc.wait(timeout=3) + except subprocess.TimeoutExpired: + _video_proc.kill() + _video_proc.wait() + time.sleep(0.15) # let DSI reach LP-11 + try: + log = open("/tmp/kiosk.log", "w") + _video_proc = subprocess.Popen( + _kiosk_args, stdout=log, + stderr=subprocess.STDOUT, env=os.environ.copy(), + ) + except Exception as e: + return jsonify({"error": f"restart failed: {e}"}), 500 + return jsonify({"status": "static-pink restarted"}), 200 + elif _video_proc is not None and _video_proc.poll() is None: + # Video mode: UDP trigger switches clip and reloads pipeline _sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) _sock.sendto(b'switch', ('127.0.0.1', 5001)) _sock.close() return jsonify({"status": "video switched"}), 200 - # fallback when video is not running + # fallback when no kiosk is running os.system("echo 0 > /sys/class/graphics/fb0/blank") return jsonify({"status": "Display ON"}), 200 elif state == "off": @@ -216,6 +240,7 @@ def control_video(): mode = data.get("mode", "") if mode == "static-pink": cmd.append("--static-pink") + _kiosk_args[:] = cmd # persist so control_display knows the mode log = open("/tmp/kiosk.log", "w") _video_proc = subprocess.Popen( cmd, diff --git a/display_test_nexio.py b/display_test_nexio.py index 720a0b5..939f757 100644 --- a/display_test_nexio.py +++ b/display_test_nexio.py @@ -185,6 +185,10 @@ def play_static_color(r: int, g: int, b: int): Uses videotestsrc pattern=solid-color so every DSI line carries the same repeating RGB triplet — any deviation in the proto_decoder output is a DSI fault. + + Listens on UDP port 5001 for a trigger packet (same as play_kiosk), which + briefly cycles the pipeline through READY→PLAYING to generate the LP→HS + startup sequence that the scope captures on Pass 1. """ Gst.init(None) @@ -214,6 +218,22 @@ def play_static_color(r: int, g: int, b: int): old, new, _ = msg.parse_state_changed() print(f"Pipeline: {old.value_nick} -> {new.value_nick}", flush=True) + def _restart_pipeline(): + """Cycle READY→PLAYING to produce the LP→HS startup the scope triggers on.""" + print("UDP trigger: restarting pipeline (READY → PLAYING)", flush=True) + pipeline.set_state(Gst.State.READY) + pipeline.set_state(Gst.State.PLAYING) + return False # GLib.idle_add one-shot + + def _udp_listener(): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('127.0.0.1', 5001)) + while True: + sock.recv(64) + GLib.idle_add(_restart_pipeline) + + threading.Thread(target=_udp_listener, daemon=True).start() + bus.connect("message", on_message) pipeline.set_state(Gst.State.PLAYING) print(f"Static colour R:{r} G:{g} B:{b} (0x{argb:08X}) — running", flush=True)