feat: add full event download pipeline

This commit is contained in:
Brian Harrison
2026-03-31 20:48:03 -04:00
parent 6a0422a6fc
commit 9f52745bb4
4 changed files with 595 additions and 129 deletions

View File

@@ -90,33 +90,90 @@ def checksum(payload: bytes) -> int:
# ── BW→S3 frame builder ───────────────────────────────────────────────────────
def build_bw_frame(sub: int, offset: int = 0) -> bytes:
def build_bw_frame(sub: int, offset: int = 0, params: bytes = bytes(10)) -> bytes:
"""
Build a BW→S3 read-command frame.
The payload is always 16 de-stuffed bytes:
[BW_CMD, 0x00, sub, 0x00, 0x00, offset, 0x00 × 10]
[BW_CMD, 0x00, sub, 0x00, 0x00, offset] + params(10 bytes)
Confirmed from BW capture analysis: payload[3] and payload[4] are always
0x00 across all observed read commands. The two-step offset lives at
payload[5]: 0x00 for the length-probe step, DATA_LEN for the data-fetch step.
The 10 params bytes (payload[6..15]) are zero for standard reads. For
keyed reads (SUBs 0A, 0C) the 4-byte waveform key lives at params[4..7]
(= payload[10..13]). For token-based reads (SUBs 1E, 1F) a single token
byte lives at params[6] (= payload[12]). Use waveform_key_params() and
token_params() helpers to build these safely.
Wire output: [ACK] [STX] dle_stuff(payload + checksum) [ETX]
Args:
sub: SUB command byte (e.g. 0x01 = FULL_CONFIG_READ)
offset: Value placed at payload[5].
Pass 0 for the probe step; pass DATA_LENGTHS[sub] for the data step.
params: 10 bytes placed at payload[6..15]. Default: all zeros.
Returns:
Complete frame bytes ready to write to the serial port / socket.
"""
payload = bytes([BW_CMD, 0x00, sub, 0x00, 0x00, offset]) + bytes(_BW_PAYLOAD_SIZE - 6)
if len(params) != 10:
raise ValueError(f"params must be exactly 10 bytes, got {len(params)}")
payload = bytes([BW_CMD, 0x00, sub, 0x00, 0x00, offset]) + params
chk = checksum(payload)
wire = bytes([ACK, STX]) + dle_stuff(payload + bytes([chk])) + bytes([ETX])
return wire
def waveform_key_params(key4: bytes) -> bytes:
"""
Build the 10-byte params block that carries a 4-byte waveform key.
Used for SUBs 0A (WAVEFORM_HEADER) and 0C (WAVEFORM_RECORD).
The key goes at params[4..7], which maps to payload[10..13].
Confirmed from 3-31-26 capture: 0A and 0C request frames carry the
4-byte record address at payload[10..13]. Probe and data-fetch steps
carry the same key in both frames.
Args:
key4: exactly 4 bytes — the opaque waveform record address returned
by the EVENT_HEADER (1E) or EVENT_ADVANCE (1F) response.
Returns:
10-byte params block with key embedded at positions [4..7].
"""
if len(key4) != 4:
raise ValueError(f"waveform key must be 4 bytes, got {len(key4)}")
p = bytearray(10)
p[4:8] = key4
return bytes(p)
def token_params(token: int = 0) -> bytes:
"""
Build the 10-byte params block that carries a single token byte.
Used for SUBs 1E (EVENT_HEADER) and 1F (EVENT_ADVANCE).
The token goes at params[6], which maps to payload[12].
Confirmed from 3-31-26 capture:
- token=0x00: first-event read / browse mode (no download marking)
- token=0xfe: download mode (causes 1F to skip partial bins and
advance to the next full record)
Args:
token: single byte to place at params[6] / payload[12].
Returns:
10-byte params block with token at position [6].
"""
p = bytearray(10)
p[6] = token
return bytes(p)
# ── Pre-built POLL frames ─────────────────────────────────────────────────────
#
# POLL (SUB 0x5B) uses the same two-step pattern as all other reads — the