77 lines
2.3 KiB
Python
77 lines
2.3 KiB
Python
#!/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/<name>.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)")
|