fix: ensure proper waveform context by always calling 0A before 0C and use all-zero params for event advancement
This commit is contained in:
33
CLAUDE.md
33
CLAUDE.md
@@ -137,14 +137,23 @@ then sends the termination frame.
|
|||||||
|
|
||||||
### SUB 1E / 1F — event iteration null sentinel and token position (FIXED, do not re-introduce)
|
### SUB 1E / 1F — event iteration null sentinel and token position (FIXED, do not re-introduce)
|
||||||
|
|
||||||
**token_params bug:** The token byte was at `params[6]` (wrong). Both 3-31-26 and 4-3-26
|
**token_params bug (FIXED):** The token byte was at `params[6]` (wrong). Both 3-31-26 and
|
||||||
BW TX captures confirm it belongs at **`params[7]`** (raw: `00 00 00 00 00 00 00 fe 00 00`).
|
4-3-26 BW TX captures confirm it belongs at **`params[7]`** (raw: `00 00 00 00 00 00 00 fe 00 00`).
|
||||||
With the wrong position the device ignores the token and 1F returns null immediately.
|
With the wrong position the device ignores the token and 1F returns null immediately.
|
||||||
|
|
||||||
|
**all-zero params required (empirically confirmed):** Even with the correct token position,
|
||||||
|
sending `token=0xFE` causes the device to return null from 1F in multi-event sessions.
|
||||||
|
All callers (`count_events`, `get_events`) must use `advance_event(browse=True)` which
|
||||||
|
sends all-zero params. The 3-31-26 capture that "confirmed" token=0xFE had only one event
|
||||||
|
stored — 1F always returns null at end-of-events, so we never actually observed 1F
|
||||||
|
successfully returning a second key with token=0xFE. Empirical evidence from live device
|
||||||
|
testing with 2+ events is definitive: **always use all-zero params for 1F.**
|
||||||
|
|
||||||
**0A context requirement:** `advance_event()` (1F) only returns a valid next-event key
|
**0A context requirement:** `advance_event()` (1F) only returns a valid next-event key
|
||||||
when a preceding `read_waveform_header()` (0A) or `read_waveform_record()` (0C) call has
|
when a preceding `read_waveform_header()` (0A) call has established device waveform
|
||||||
established device waveform context for the current key. Calling 1F cold (after only 1E,
|
context for the current key. Call 0A before every event in the loop, not just the first.
|
||||||
with no 0A/0C) returns the null sentinel regardless of how many events are stored.
|
Calling 1F cold (after only 1E, with no 0A) returns the null sentinel regardless of how
|
||||||
|
many events are stored.
|
||||||
|
|
||||||
**1F response layout:** The next event's key IS at `data_rsp.data[11:15]` (= payload[16:20]).
|
**1F response layout:** The next event's key IS at `data_rsp.data[11:15]` (= payload[16:20]).
|
||||||
Confirmed from 4-3-26 browse-mode S3 captures:
|
Confirmed from 4-3-26 browse-mode S3 captures:
|
||||||
@@ -162,15 +171,19 @@ echo) — in both cases, all zeros means "no more events."
|
|||||||
= sample-count offset to the next event key (key1 = key0 + this offset). If offset == 0,
|
= sample-count offset to the next event key (key1 = key0 + this offset). If offset == 0,
|
||||||
there is only one event.
|
there is only one event.
|
||||||
|
|
||||||
**Correct iteration pattern (confirmed from 4-3-26 capture):**
|
**Correct iteration pattern (confirmed empirically with live device, 2+ events):**
|
||||||
```
|
```
|
||||||
1E(all zeros) → key0, trailing0 ← trailing0 non-zero if event 1 exists
|
1E(all zeros) → key0, trailing0 ← trailing0 non-zero if event 1 exists
|
||||||
0A(key0) ← establishes device context
|
0A(key0) ← REQUIRED: establishes device context
|
||||||
1F(token=0xFE at params[7]) → key1, trailing1
|
0C(key0) [+ 5A(key0) for get_events] ← read record data
|
||||||
0A(key1) ← establishes context for next advance
|
1F(all zeros / browse=True) → key1 ← use all-zero params, NOT token=0xFE
|
||||||
1F(token=0xFE) → null ← done
|
0A(key1) ← REQUIRED before each advance
|
||||||
|
0C(key1) [+ 5A(key1) for get_events]
|
||||||
|
1F(all zeros) → null ← done
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`advance_event(browse=True)` sends all-zero params; `advance_event()` default (browse=False)
|
||||||
|
sends token=0xFE and is NOT used by any caller.
|
||||||
`advance_event()` returns `(key4, event_data8)`.
|
`advance_event()` returns `(key4, event_data8)`.
|
||||||
Callers (`count_events`, `get_events`) loop while `data8[4:8] != b"\x00\x00\x00\x00"`.
|
Callers (`count_events`, `get_events`) loop while `data8[4:8] != b"\x00\x00\x00\x00"`.
|
||||||
|
|
||||||
|
|||||||
@@ -275,23 +275,21 @@ class MiniMateClient:
|
|||||||
|
|
||||||
events: list[Event] = []
|
events: list[Event] = []
|
||||||
idx = 0
|
idx = 0
|
||||||
is_first = True
|
|
||||||
|
|
||||||
while data8[4:8] != b"\x00\x00\x00\x00":
|
while data8[4:8] != b"\x00\x00\x00\x00":
|
||||||
log.info("get_events: record %d key=%s", idx, key4.hex())
|
log.info("get_events: record %d key=%s", idx, key4.hex())
|
||||||
ev = Event(index=idx)
|
ev = Event(index=idx)
|
||||||
ev._waveform_key = key4 # stored so download_waveform() can re-use it
|
ev._waveform_key = key4 # stored so download_waveform() can re-use it
|
||||||
|
|
||||||
# First event: call 0A to verify it's a full record (0x30 length).
|
# Always call 0A before 0C to establish device waveform context.
|
||||||
# Subsequent keys come from 1F(0xFE) which guarantees full records,
|
# The device requires 0A context for both 0C and the subsequent 1F.
|
||||||
# so we skip 0A for those — exactly matching Blastware behaviour.
|
# (Earlier code skipped 0A for events after the first — confirmed wrong.)
|
||||||
proceed = True
|
proceed = True
|
||||||
if is_first:
|
|
||||||
try:
|
try:
|
||||||
_hdr, rec_len = proto.read_waveform_header(key4)
|
_hdr, rec_len = proto.read_waveform_header(key4)
|
||||||
if rec_len < 0x30:
|
if rec_len < 0x30:
|
||||||
log.warning(
|
log.warning(
|
||||||
"get_events: first key=%s is partial (len=0x%02X) — skipping",
|
"get_events: key=%s is partial (len=0x%02X) — skipping",
|
||||||
key4.hex(), rec_len,
|
key4.hex(), rec_len,
|
||||||
)
|
)
|
||||||
proceed = False
|
proceed = False
|
||||||
@@ -301,7 +299,6 @@ class MiniMateClient:
|
|||||||
key4.hex(), exc,
|
key4.hex(), exc,
|
||||||
)
|
)
|
||||||
proceed = False
|
proceed = False
|
||||||
is_first = False
|
|
||||||
|
|
||||||
if proceed:
|
if proceed:
|
||||||
# SUB 0C — full waveform record (peak values, timestamp, "Project:" string)
|
# SUB 0C — full waveform record (peak values, timestamp, "Project:" string)
|
||||||
@@ -357,9 +354,18 @@ class MiniMateClient:
|
|||||||
events.append(ev)
|
events.append(ev)
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
# SUB 1F — advance to the next full waveform record key
|
# SUB 1F — advance to the next record key.
|
||||||
|
# Uses browse mode (all-zero params) — empirically confirmed to work on
|
||||||
|
# this device. token=0xFE (download mode) returns null regardless of
|
||||||
|
# context, even after 0A+0C+5A. The 3-31-26 capture only had one event
|
||||||
|
# so we never saw 1F actually return a second key in download mode;
|
||||||
|
# the token=0xFE assumption was never validated for multi-event iteration.
|
||||||
try:
|
try:
|
||||||
key4, data8 = proto.advance_event()
|
key4, data8 = proto.advance_event(browse=True)
|
||||||
|
log.info(
|
||||||
|
"get_events: 1F → key=%s trailing=%s",
|
||||||
|
key4.hex(), data8[4:8].hex(),
|
||||||
|
)
|
||||||
except ProtocolError as exc:
|
except ProtocolError as exc:
|
||||||
log.warning("get_events: 1F failed: %s — stopping iteration", exc)
|
log.warning("get_events: 1F failed: %s — stopping iteration", exc)
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user