#!/usr/bin/env python3 """Decode a captured frame as SBUS: UART 100 kbit/s 8E2, 25-byte frame, 16 channels x 11 bits. Usage: .venv/bin/python tools/decode.py captures/.npz """ import sys import numpy as np BIT_US = 10.0 path = sys.argv[1] d = np.load(path) volts = d["volts"] srate = float(d["srate"]) dt_us = 1e6 / srate # idle level dominates the record; UART logic 1 == idle regardless of # physical polarity (this line is standard inverted SBUS: idle low) idle = np.median(volts) p10, p90 = np.percentile(volts, [10, 90]) active = p10 if abs(p10 - idle) > abs(p90 - idle) else p90 thr = (idle + active) / 2 if active > idle: sig = (volts < thr).astype(np.int8) # pulses up -> logic 0 else: sig = (volts > thr).astype(np.int8) edges = np.flatnonzero(np.diff(sig)) + 1 starts = np.concatenate(([0], edges)) ends = np.concatenate((edges, [len(sig)])) levels = sig[starts] dur_us = (ends - starts) * dt_us stream = [] for lv, du in zip(levels[1:-1], dur_us[1:-1]): stream.extend([int(lv)] * max(1, round(du / BIT_US))) # trailing idle of last stop bits is trimmed by run cut; pad with idle-high stream.extend([1] * 24) # deframe 8E2: start=0, 8 data LSB-first, even parity, 2 stop=1 i = 0 frames = [] errors = [] while i + 12 <= len(stream): if stream[i] == 1: i += 1 continue data = stream[i + 1 : i + 9] par = stream[i + 9] stops = stream[i + 10 : i + 12] byte = sum(b << k for k, b in enumerate(data)) if par != (sum(data) & 1): errors.append((len(frames), "parity")) if stops != [1, 1]: errors.append((len(frames), f"stop={stops}")) frames.append(byte) i += 12 print(f"decoded {len(frames)} bytes, errors: {errors if errors else 'none'}") print("hex:", " ".join(f"{b:02X}" for b in frames)) if len(frames) >= 25 and frames[0] == 0x0F: payload = frames[1:23] flags = frames[23] footer = frames[24] bits = 0 for k, b in enumerate(payload): bits |= b << (8 * k) ch = [(bits >> (11 * n)) & 0x7FF for n in range(16)] print("\nSBUS frame OK" if footer == 0x00 else f"\nfooter unexpected: {footer:02X}") print("channels:", ch) print(f"flags: 0x{flags:02X} (bit0=ch17 bit1=ch18 bit2=frame_lost bit3=failsafe)") else: print("not a valid SBUS frame (no 0x0F header)")