feat: fetch event-time metadata from SUB 5A bulk waveform stream
Add read_bulk_waveform_stream() to MiniMateProtocol and wire it into get_events() so each event gets authoritative client/operator/sensor_location from the A5 frames recorded at event-time, not the current compliance config. - framing.py: bulk_waveform_params() and bulk_waveform_term_params() helpers (probe/chunk params and termination params for SUB 5A, confirmed from 1-2-26 BW TX capture) - protocol.py: read_bulk_waveform_stream(key4, stop_after_metadata=True) — probe + chunk loop (counter += 0x0400), early stop when b"Project:" found (A5[7] of 9), then sends termination at offset=0x005A - client.py: _decode_a5_metadata_into() needle-searches concatenated A5 frame data for Project/Client/User Name/Seis Loc/Extended Notes; get_events() now calls SUB 5A after each SUB 0C, overwriting project_info with event-time values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -182,6 +182,83 @@ def token_params(token: int = 0) -> bytes:
|
||||
return bytes(p)
|
||||
|
||||
|
||||
def bulk_waveform_params(key4: bytes, counter: int, *, is_probe: bool = False) -> bytes:
|
||||
"""
|
||||
Build the 10-byte params block for SUB 5A (BULK_WAVEFORM_STREAM) requests.
|
||||
|
||||
Confirmed 2026-04-02 from 1-2-26 BW TX capture analysis:
|
||||
|
||||
Probe / first request (is_probe=True, counter=0):
|
||||
params[0] = 0x00
|
||||
params[1:5] = key4 (all 4 key bytes; counter overlaps key4[2:4] = 0x0000)
|
||||
params[5:] = zeros
|
||||
|
||||
Regular chunk requests (is_probe=False):
|
||||
params[0] = 0x00
|
||||
params[1:3] = key4[0:2] (first 2 key bytes as session handle)
|
||||
params[3:5] = counter (BE uint16) (chunk position, increments by 0x0400)
|
||||
params[5:] = zeros
|
||||
|
||||
Termination request: DO NOT use this helper — see bulk_waveform_term_params().
|
||||
|
||||
Args:
|
||||
key4: 4-byte waveform key from EVENT_HEADER (1E) response.
|
||||
counter: Chunk position counter (uint16 BE). Pass 0 for probe.
|
||||
is_probe: If True, embed full key4 (probe step only).
|
||||
|
||||
Returns:
|
||||
10-byte params block.
|
||||
"""
|
||||
if len(key4) != 4:
|
||||
raise ValueError(f"waveform key must be 4 bytes, got {len(key4)}")
|
||||
p = bytearray(10)
|
||||
p[0] = 0x00
|
||||
p[1] = key4[0]
|
||||
p[2] = key4[1]
|
||||
if is_probe:
|
||||
# Full key4; counter=0 is implied (overlaps with key4[2:4] which must be 0x0000)
|
||||
p[3] = key4[2]
|
||||
p[4] = key4[3]
|
||||
else:
|
||||
p[3] = (counter >> 8) & 0xFF
|
||||
p[4] = counter & 0xFF
|
||||
return bytes(p)
|
||||
|
||||
|
||||
def bulk_waveform_term_params(key4: bytes, counter: int) -> bytes:
|
||||
"""
|
||||
Build the 10-byte params block for the SUB 5A termination request.
|
||||
|
||||
The termination request uses offset=0x005A and a DIFFERENT params layout —
|
||||
the leading 0x00 byte is dropped, key4[0:2] shifts to params[0:2], and the
|
||||
counter high byte is at params[2]:
|
||||
|
||||
params[0] = key4[0]
|
||||
params[1] = key4[1]
|
||||
params[2] = (counter >> 8) & 0xFF
|
||||
params[3:] = zeros
|
||||
|
||||
Counter for the termination request = last_regular_counter + 0x0400.
|
||||
|
||||
Confirmed from 1-2-26 BW TX capture: final request (frame 83) uses
|
||||
offset=0x005A, params[0:3] = key4[0:2] + term_counter_hi.
|
||||
|
||||
Args:
|
||||
key4: 4-byte waveform key.
|
||||
counter: Termination counter (= last regular counter + 0x0400).
|
||||
|
||||
Returns:
|
||||
10-byte params block.
|
||||
"""
|
||||
if len(key4) != 4:
|
||||
raise ValueError(f"waveform key must be 4 bytes, got {len(key4)}")
|
||||
p = bytearray(10)
|
||||
p[0] = key4[0]
|
||||
p[1] = key4[1]
|
||||
p[2] = (counter >> 8) & 0xFF
|
||||
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