From bc1d5bdc303d4b54a8ba4b750a18a866349b7b17 Mon Sep 17 00:00:00 2001 From: david rice Date: Fri, 24 Apr 2026 15:24:27 +0100 Subject: [PATCH] Chnages --- __pycache__/proto_decoder.cpython-312.pyc | Bin 0 -> 20185 bytes device_server.py | 8 ++- display_test_nexio.py | 50 ++++++++++++- mipi_test_interactive.py | 4 +- proto_decoder.py | 82 ++++++++++++++++------ 5 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 __pycache__/proto_decoder.cpython-312.pyc diff --git a/__pycache__/proto_decoder.cpython-312.pyc b/__pycache__/proto_decoder.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c32e0cf6420d08b4244fad56a47843f1bf253b19 GIT binary patch literal 20185 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 TeeEv{mfz`rsOR!OVDP^IN?3;r literal 0 HcmV?d00001 diff --git a/device_server.py b/device_server.py index 11d3b00..f5b6642 100644 --- a/device_server.py +++ b/device_server.py @@ -212,16 +212,20 @@ def control_video(): if _video_proc is not None and _video_proc.poll() is None: return jsonify({"status": "already_running", "pid": _video_proc.pid}), 200 try: + cmd = ["python3", KIOSK_SCRIPT] + mode = data.get("mode", "") + if mode == "static-pink": + cmd.append("--static-pink") log = open("/tmp/kiosk.log", "w") _video_proc = subprocess.Popen( - ["python3", KIOSK_SCRIPT], + cmd, stdout=log, stderr=subprocess.STDOUT, env=os.environ.copy(), ) except Exception as e: return jsonify({"error": f"failed to launch kiosk: {e}"}), 500 - return jsonify({"status": "started", "pid": _video_proc.pid}), 200 + return jsonify({"status": "started", "mode": mode or "video", "pid": _video_proc.pid}), 200 elif action == "stop": if _video_proc is not None and _video_proc.poll() is None: diff --git a/display_test_nexio.py b/display_test_nexio.py index 21150dd..720a0b5 100644 --- a/display_test_nexio.py +++ b/display_test_nexio.py @@ -180,5 +180,53 @@ def play_kiosk(): except KeyboardInterrupt: pipeline.set_state(Gst.State.NULL) +def play_static_color(r: int, g: int, b: int): + """Display a solid colour using GStreamer videotestsrc (no video file required). + + 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. + """ + Gst.init(None) + + argb = (0xFF << 24) | (r << 16) | (g << 8) | b + + SINK_STR = ("videoconvert ! video/x-raw,format=BGRx ! " + "kmssink driver-name=mxsfb-drm connector-id=37 plane-id=31 can-scale=false") + pipeline_str = ( + f"videotestsrc pattern=solid-color foreground-color={argb} ! " + f"video/x-raw,width=1280,height=800,framerate=60/1 ! " + f"{SINK_STR}" + ) + + pipeline = Gst.parse_launch(pipeline_str) + bus = pipeline.get_bus() + bus.add_signal_watch() + + loop = GLib.MainLoop() + + def on_message(bus, msg): + if msg.type == Gst.MessageType.ERROR: + err, debug = msg.parse_error() + print(f"GStreamer Error: {err}\nDebug: {debug}", flush=True) + loop.quit() + elif msg.type == Gst.MessageType.STATE_CHANGED: + if msg.src == pipeline: + old, new, _ = msg.parse_state_changed() + print(f"Pipeline: {old.value_nick} -> {new.value_nick}", flush=True) + + 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) + + try: + loop.run() + except KeyboardInterrupt: + pipeline.set_state(Gst.State.NULL) + + if __name__ == "__main__": - play_kiosk() + import sys + if "--static-pink" in sys.argv: + play_static_color(255, 51, 187) # R:255 G:51 B:187 + else: + play_kiosk() diff --git a/mipi_test_interactive.py b/mipi_test_interactive.py index 2c88ff7..42b91c3 100644 --- a/mipi_test_interactive.py +++ b/mipi_test_interactive.py @@ -841,8 +841,8 @@ def _append_flicker_log(ts: str, iteration: int, m: LPMetrics) -> None: def _start_video() -> None: try: - requests.put(VIDEO_URL, json={"action": "start"}, timeout=3) - print(" VIDEO: kiosk player started.") + requests.put(VIDEO_URL, json={"action": "start", "mode": "static-pink"}, timeout=3) + print(" VIDEO: static-pink display started.") except Exception as e: print(f" WARNING: video start failed: {e}") diff --git a/proto_decoder.py b/proto_decoder.py index 0940d33..71305a5 100644 --- a/proto_decoder.py +++ b/proto_decoder.py @@ -104,23 +104,51 @@ def find_clock_edges(t_clk, v_clk, threshold=0.0): def find_hs_start(t_dat, v_dat, t_clk=None, window_ns=500.0): """ - Find the start of the main HS burst in the DAT trace. + Find the start of the post-LP HS burst in the DAT trace. - The proto capture often starts mid-HS (previous packet), so we: - 1. Find the LP quiet region (both LP-11 and LP-low have low differential std) - 2. Find the first sustained oscillation AFTER that quiet region + For LP-triggered captures (trigger = DAT D+ falling at LP-11→LP-01 transition): + - CLK is in continuous HS mode throughout (215 MHz running) + - DAT shows LP-01 (diff ≈ -1 V) near t=0, preceded by HS data from the + previous line and possibly an earlier LP-01 at the start of the capture + - LP-00 follows LP-01 briefly (~50-200 ns), then the new HS burst begins + - To avoid the LP-01 from the previous line (at capture start), search + from N//4 onwards — the trigger LP-01 is at the capture midpoint (t=0) - Returns: index into t_dat of approximate HS burst start, or None. + Returns index into t_dat just past LP-00, ready for CLK-edge sampling. + Falls back to original std-based method for HS-triggered captures. """ - dt_ns = float(np.median(np.diff(t_dat))) * 1e9 - win = max(1, int(1.0 / dt_ns)) # 1 ns rolling window - min_run = max(5, int(5.0 / dt_ns)) # at least 5 ns continuous + dt_ns = float(np.median(np.diff(t_dat))) * 1e9 + N = len(v_dat) - rstd = np.array([v_dat[max(0, i - win):i + 1].std() for i in range(len(v_dat))]) - OSC_THRESH = 0.04 # 40 mV — HS oscillation - QUIET_THRESH = 0.02 # 20 mV — LP quiet (LP-11 differential ≈ 0, low std) + # --- LP-triggered path --- + # LP-01: D+ = 0 V, D- = high → diff strongly negative (< -0.5 V for ≥ 20 ns) + LP01_THRESH = -0.5 + min_lp01 = max(2, int(20.0 / dt_ns)) + search_from = N // 4 # skip any LP-01 fragment at capture start + + run = 0 + lp01_end = None + for i in range(search_from, N): + if v_dat[i] < LP01_THRESH: + run += 1 + else: + if run >= min_lp01: + lp01_end = i + break + run = 0 + + if lp01_end is not None: + # Skip 200 ns past LP-01 end to clear LP-00, then hand off to bit decoder + skip = max(1, int(200.0 / dt_ns)) + return min(lp01_end + skip, N - 1) + + # --- Fallback: HS-triggered captures (original rolling-std method) --- + win = max(1, int(1.0 / dt_ns)) + min_run = max(5, int(5.0 / dt_ns)) + rstd = np.array([v_dat[max(0, i - win):i + 1].std() for i in range(N)]) + OSC_THRESH = 0.04 + QUIET_THRESH = 0.02 - # Step 1: find a quiet (LP) region of at least 200 ns quiet_min_run = max(5, int(200.0 / dt_ns)) quiet_end = None run_len = 0 @@ -133,9 +161,8 @@ def find_hs_start(t_dat, v_dat, t_clk=None, window_ns=500.0): run_len = 0 if quiet_end is None: - return None # no LP region found + return None - # Step 2: find first sustained oscillation after the LP region run_start = None run_len = 0 for i in range(quiet_end, len(rstd)): @@ -273,7 +300,7 @@ def decode_capture(cap_num: int, data_dir: Path, verbose: bool = True): dt_ns = float(np.median(np.diff(t_dat))) * 1e9 if verbose: - print(f" Window: {t_dat[0]*1e6:.2f}..{t_dat[-1]*1e6:.2f} µs ({len(t_dat)} samples, {dt_ns:.0f} ps/sample)") + print(f" Window: {t_dat[0]*1e6:.2f}..{t_dat[-1]*1e6:.2f} µs ({len(t_dat)} samples, {dt_ns*1000:.0f} ps/sample)") # Find HS burst start hs_start_idx = find_hs_start(t_dat, v_dat) @@ -299,18 +326,33 @@ def decode_capture(cap_num: int, data_dir: Path, verbose: bool = True): print(" ERROR: Too few bits decoded") return None - raw_bytes = bits_to_bytes(bits) + # Try all 8 bit-phase offsets to handle framing uncertainty from LP-00 CLK edges. + # LP-00 CLK edges before HS starts produce garbage bits; the correct phase is + # the one where 0xB8 appears earliest in the byte stream. + raw_bytes = None + sync_idx = None + best_phase = 0 + best_sync = len(bits) # sentinel: "not found" + for phase in range(8): + rb = bits_to_bytes(bits[phase:]) + si = find_sync_byte(rb) + if si is not None and si < best_sync: + best_sync = si + best_phase = phase + raw_bytes = rb + sync_idx = si + + if raw_bytes is None: + raw_bytes = bits_to_bytes(bits) - # Find sync byte alignment - sync_idx = find_sync_byte(raw_bytes) if sync_idx is None: if verbose: - print(f" WARNING: HS sync byte (0x{HS_SYNC_BYTE:02X}) not found — using raw byte 0 as start") + print(f" WARNING: HS sync byte (0x{HS_SYNC_BYTE:02X}) not found in any bit phase — using raw byte 0") sync_idx = 0 else: if verbose: t_sync = raw_bytes[sync_idx][0] - print(f" HS sync byte found at byte {sync_idx} (t={t_sync:.0f} ns)") + print(f" HS sync byte found at byte {sync_idx} (t={t_sync:.0f} ns, bit phase={best_phase})") # Data bytes after sync data_bytes = raw_bytes[sync_idx + 1:] # skip the sync byte itself