feat: add full event download pipeline
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user