cart_firmware_added
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rigorous comparison of two SBUS captures (old remote vs our firmware).
|
||||
|
||||
Usage: .venv/bin/python tools/compare.py captures/a.npz captures/b.npz
|
||||
"""
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def extract(path):
|
||||
d = np.load(path)
|
||||
v = d["volts"]
|
||||
srate = float(d["srate"])
|
||||
idle = np.median(v)
|
||||
p10, p90 = np.percentile(v, [10, 90])
|
||||
active = p10 if abs(p10 - idle) > abs(p90 - idle) else p90
|
||||
thr = (idle + active) / 2
|
||||
phys_hi = v > thr # physical high (pulse)
|
||||
# plateau levels: median of samples well inside each state
|
||||
lvl_hi = np.median(v[phys_hi])
|
||||
lvl_lo = np.median(v[~phys_hi])
|
||||
# runs
|
||||
sig = phys_hi.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) * 1e6 / srate
|
||||
# burst envelope: first to last physical-high sample
|
||||
hi_idx = np.flatnonzero(phys_hi)
|
||||
envelope_us = (hi_idx[-1] - hi_idx[0] + 1) * 1e6 / srate
|
||||
# inner runs (drop leading/trailing idle)
|
||||
runs = [(int(l), float(du)) for l, du in zip(levels[1:-1], dur_us[1:-1])]
|
||||
# UART logic: logic1 == idle state; idle here is physical low
|
||||
stream = []
|
||||
for l, du in runs:
|
||||
n = max(1, round(du / 10.0))
|
||||
stream.extend([1 - l] * n) # physical high -> logic 0
|
||||
stream.extend([1] * 24)
|
||||
frames = []
|
||||
i = 0
|
||||
while i + 12 <= len(stream):
|
||||
if stream[i] == 1:
|
||||
i += 1
|
||||
continue
|
||||
byte = sum(b << k for k, b in enumerate(stream[i + 1 : i + 9]))
|
||||
frames.append(byte)
|
||||
i += 12
|
||||
# bit clock estimate: envelope should be 299 bits (last stop bits merge w/ idle)
|
||||
n_units = round(envelope_us / 10.0)
|
||||
bit_us = envelope_us / n_units
|
||||
return {
|
||||
"lvl_hi": lvl_hi,
|
||||
"lvl_lo": lvl_lo,
|
||||
"runs": runs,
|
||||
"frames": frames,
|
||||
"envelope_us": envelope_us,
|
||||
"bit_us": bit_us,
|
||||
"vmin": float(v.min()),
|
||||
"vmax": float(v.max()),
|
||||
}
|
||||
|
||||
|
||||
a_path, b_path = sys.argv[1], sys.argv[2]
|
||||
A, B = extract(a_path), extract(b_path)
|
||||
|
||||
print(f"{'':24s} {'A: ' + a_path:>28s} {'B: ' + b_path:>28s}")
|
||||
print(f"{'bytes decoded':24s} {len(A['frames']):>28d} {len(B['frames']):>28d}")
|
||||
ha = " ".join(f"{x:02X}" for x in A["frames"])
|
||||
hb = " ".join(f"{x:02X}" for x in B["frames"])
|
||||
print(f"frames identical: {A['frames'] == B['frames']}")
|
||||
print(" A:", ha)
|
||||
print(" B:", hb)
|
||||
print(f"{'run count':24s} {len(A['runs']):>28d} {len(B['runs']):>28d}")
|
||||
qa = [round(du / 10) for _, du in A["runs"]]
|
||||
qb = [round(du / 10) for _, du in B["runs"]]
|
||||
la = [l for l, _ in A["runs"]]
|
||||
lb = [l for l, _ in B["runs"]]
|
||||
print(f"quantized run pattern identical: {qa == qb and la == lb}")
|
||||
print(f"{'envelope, us':24s} {A['envelope_us']:>28.2f} {B['envelope_us']:>28.2f}")
|
||||
print(f"{'bit time, us':24s} {A['bit_us']:>28.4f} {B['bit_us']:>28.4f}")
|
||||
print(f"{'-> baud':24s} {1e6/A['bit_us']:>28.1f} {1e6/B['bit_us']:>28.1f}")
|
||||
print(f"{'high plateau, V':24s} {A['lvl_hi']:>28.3f} {B['lvl_hi']:>28.3f}")
|
||||
print(f"{'low plateau, V':24s} {A['lvl_lo']:>28.3f} {B['lvl_lo']:>28.3f}")
|
||||
print(f"{'abs min/max, V':24s} {A['vmin']:>14.2f}/{A['vmax']:>12.2f} {B['vmin']:>14.2f}/{B['vmax']:>12.2f}")
|
||||
|
||||
# worst run deviation from ideal 10us grid
|
||||
da = max(abs(du - 10 * round(du / 10)) for _, du in A["runs"])
|
||||
db = max(abs(du - 10 * round(du / 10)) for _, du in B["runs"])
|
||||
print(f"{'worst grid dev, us':24s} {da:>28.2f} {db:>28.2f}")
|
||||
Reference in New Issue
Block a user