fix(protocol): correct A5 frame classification and chunk counter formula
This commit is contained in:
@@ -118,9 +118,11 @@ S3→BW (response):
|
|||||||
Both differences confirmed by reproducing Blastware's exact wire bytes from the 1-2-26
|
Both differences confirmed by reproducing Blastware's exact wire bytes from the 1-2-26
|
||||||
BW TX capture. All 10 frames verified.
|
BW TX capture. All 10 frames verified.
|
||||||
|
|
||||||
### SUB 5A — chunk counter is monotonic (CORRECTED 2026-04-06)
|
### SUB 5A — chunk counter formula (FINAL CORRECTION 2026-04-24)
|
||||||
|
|
||||||
**Chunk counters are `chunk_num * 0x0400` for ALL chunks including chunk 1.**
|
**Chunk counter = `key4[2:4] + (chunk_num - 1) * 0x0400` for ALL chunks.**
|
||||||
|
|
||||||
|
where `key4[2:4] = (key4[2] << 8) | key4[3]` is the event's circular-buffer base offset.
|
||||||
|
|
||||||
The 4-2-26 BW TX capture showed `counter=0x1004` for chunk 1 of event key `01110000`, which
|
The 4-2-26 BW TX capture showed `counter=0x1004` for chunk 1 of event key `01110000`, which
|
||||||
led to `_CHUNK1_COUNTER = 0x1004` being hardcoded as a special case. This was a Blastware
|
led to `_CHUNK1_COUNTER = 0x1004` being hardcoded as a special case. This was a Blastware
|
||||||
@@ -130,9 +132,14 @@ immediately and streams all frames correctly.
|
|||||||
|
|
||||||
The 4-3-26 capture confirms the pattern for a second event (key `0111245a`):
|
The 4-3-26 capture confirms the pattern for a second event (key `0111245a`):
|
||||||
chunk 1 = `0x245A`, chunk 2 = `0x285A`, chunk 3 = `0x2C5A` (each +0x0400). Blastware's
|
chunk 1 = `0x245A`, chunk 2 = `0x285A`, chunk 3 = `0x2C5A` (each +0x0400). Blastware's
|
||||||
true formula is `key4[2:4] + n * 0x0400` — but since `key4[2:4]` of the first event is
|
true formula is `key4[2:4] + (chunk_num - 1) * 0x0400`.
|
||||||
`0x0000`, `n * 0x0400` produces the right result. The device does not strictly validate the
|
|
||||||
counter and streams data for any valid 5A request; using `chunk_num * 0x0400` is correct.
|
**2026-04-24 CORRECTION — `n * 0x0400` is WRONG for non-first events.** For event key
|
||||||
|
`01110000`, `key4[2:4] == 0x0000` so the old `chunk_num * 0x0400` formula was accidentally
|
||||||
|
correct. For keys with `key4[2:4] != 0` (e.g. key `01111884`, offset `0x1884`), the old
|
||||||
|
formula sends counters pointing into the wrong buffer region — the device returns data from
|
||||||
|
a completely different stored event and `b"Project:"` never appears in the stream.
|
||||||
|
Use `key4[2:4] + (chunk_num - 1) * 0x0400` exclusively.
|
||||||
|
|
||||||
### SUB 5A — params are 11 bytes for chunk frames, 10 for termination
|
### SUB 5A — params are 11 bytes for chunk frames, 10 for termination
|
||||||
|
|
||||||
|
|||||||
@@ -1231,21 +1231,23 @@ Two critical differences from `build_bw_frame`:
|
|||||||
| Frame | offset_word | counter | params | Purpose |
|
| Frame | offset_word | counter | params | Purpose |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Probe | `0x1004` | `0x0000` | 10 bytes (`bulk_waveform_params(0)`) | Initiate transfer |
|
| Probe | `0x1004` | `0x0000` | 10 bytes (`bulk_waveform_params(0)`) | Initiate transfer |
|
||||||
| Chunk 1 | `0x1004` | `0x0400` | 11 bytes | First data chunk |
|
| Chunk 1 | `0x1004` | `key4[2:4]` | 11 bytes | First data chunk |
|
||||||
| Chunk 2 | `0x1004` | `0x0800` | 11 bytes | Second chunk |
|
| Chunk 2 | `0x1004` | `key4[2:4] + 0x0400` | 11 bytes | Second chunk |
|
||||||
| Chunk N | `0x1004` | `N * 0x0400` | 11 bytes | Nth chunk |
|
| Chunk N | `0x1004` | `key4[2:4] + (N-1) * 0x0400` | 11 bytes | Nth chunk |
|
||||||
| … | … | … | … | … |
|
| … | … | … | … | … |
|
||||||
| Termination | `0x005A` | `last + 0x0400` | 10 bytes | End transfer |
|
| Termination | `0x005A` | `key4[2:4] + N * 0x0400` | 10 bytes | End transfer |
|
||||||
|
|
||||||
> ⚠️ **2026-04-06 CORRECTED — chunk counter is monotonic for ALL chunks.**
|
> ⚠️ **2026-04-06 CORRECTED — chunk counter is `key4[2:4] + (N-1) * 0x0400`.**
|
||||||
> The 4-2-26 BW TX capture showed counter=0x1004 for chunk 1, which was hardcoded as a
|
> The 4-2-26 BW TX capture showed counter=0x1004 for chunk 1 of key `01110000`, leading to
|
||||||
> special case. This was a Blastware artifact. Empirically confirmed: counter=0x0400 for
|
> an interim "monotonic n * 0x0400" formula. This was accidentally correct because
|
||||||
> chunk 1 works correctly; counter=0x1004 causes the device to time out. The device does
|
> `key4[2:4] == 0x0000` for that event.
|
||||||
> NOT strictly validate the counter value — it streams data for any valid 5A request for
|
>
|
||||||
> the given key. Use `chunk_num * 0x0400` (monotonic) for all chunks.
|
> **2026-04-24 FINAL CORRECTION:** The counter is an absolute circular-buffer address.
|
||||||
> BW's true internal formula is `key4[2:4] + n * 0x0400`. For event 1 (key `01110000`)
|
> BW's true formula is `key4[2:4] + (chunk_num - 1) * 0x0400` where `key4[2:4]` is the
|
||||||
> this equals `n * 0x0400` since `key4[2:4] = 0x0000`. The monotonic formula is correct
|
> event's storage base offset (`(key4[2]<<8) | key4[3]`). For keys where
|
||||||
> for all keys encountered on this device.
|
> `key4[2:4] != 0x0000` (e.g. key `01111884`), using `n * 0x0400` sends requests into the
|
||||||
|
> wrong buffer region — the device returns data from a completely different event and
|
||||||
|
> `b"Project:"` never appears in the stream. Confirmed correct 2026-04-24.
|
||||||
|
|
||||||
The `stop_after_metadata=True` flag causes the loop to stop as soon as `b"Project:"` is
|
The `stop_after_metadata=True` flag causes the loop to stop as soon as `b"Project:"` is
|
||||||
found in the accumulated A5 frame data, typically after 4–9 chunks. A termination frame
|
found in the accumulated A5 frame data, typically after 4–9 chunks. A termination frame
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ def classify_frame(frame: S3Frame) -> str:
|
|||||||
|
|
||||||
Returns one of:
|
Returns one of:
|
||||||
"terminator" — page_key == 0x0000
|
"terminator" — page_key == 0x0000
|
||||||
"probe_or_strt" — data contains b"STRT" (the initial probe response)
|
"probe_or_strt" — data contains b"STRT\xff\xfe" (the initial probe response)
|
||||||
"metadata" — data contains ASCII compliance-config markers
|
"metadata" — data contains ASCII compliance-config markers
|
||||||
"waveform" — predominantly binary (< 20 % printable ASCII)
|
"waveform" — predominantly binary (< 20 % printable ASCII)
|
||||||
"unknown" — none of the above criteria matched
|
"unknown" — none of the above criteria matched
|
||||||
@@ -539,7 +539,7 @@ def classify_frame(frame: S3Frame) -> str:
|
|||||||
if frame.page_key == 0x0000:
|
if frame.page_key == 0x0000:
|
||||||
return "terminator"
|
return "terminator"
|
||||||
data = bytes(frame.data)
|
data = bytes(frame.data)
|
||||||
if b"STRT" in data:
|
if b"STRT\xff\xfe" in data:
|
||||||
return "probe_or_strt"
|
return "probe_or_strt"
|
||||||
if any(m in data for m in _METADATA_FRAME_MARKERS):
|
if any(m in data for m in _METADATA_FRAME_MARKERS):
|
||||||
return "metadata"
|
return "metadata"
|
||||||
@@ -677,6 +677,7 @@ def write_blastware_file(
|
|||||||
term_frame = None
|
term_frame = None
|
||||||
|
|
||||||
all_bytes = bytearray()
|
all_bytes = bytearray()
|
||||||
|
seen_metadata = False
|
||||||
|
|
||||||
for fi, frame in enumerate(body_frames):
|
for fi, frame in enumerate(body_frames):
|
||||||
ftype = classify_frame(frame)
|
ftype = classify_frame(frame)
|
||||||
@@ -686,11 +687,20 @@ def write_blastware_file(
|
|||||||
# Probe frame: always process regardless of classification.
|
# Probe frame: always process regardless of classification.
|
||||||
# It holds the STRT record; probe_skip positions us past it.
|
# It holds the STRT record; probe_skip positions us past it.
|
||||||
skip = probe_skip
|
skip = probe_skip
|
||||||
elif ftype == "waveform":
|
elif seen_metadata:
|
||||||
|
# Drop all frames that come after the compliance/metadata block.
|
||||||
|
# (e.g. extra waveform chunks fetched after stop_after_metadata)
|
||||||
|
log.debug(
|
||||||
|
"write_blastware_file: frame %d after metadata — skipping", fi
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
elif ftype in ("waveform", "metadata"):
|
||||||
skip = 13 if fi == 1 else 12
|
skip = 13 if fi == 1 else 12
|
||||||
|
if ftype == "metadata":
|
||||||
|
seen_metadata = True
|
||||||
else:
|
else:
|
||||||
# Skip metadata, probe_or_strt, and unknown frames.
|
# Skip probe_or_strt and unknown frames.
|
||||||
if b"STRT" in bytes(frame.data):
|
if b"STRT\xff\xfe" in bytes(frame.data):
|
||||||
log.warning(
|
log.warning(
|
||||||
"write_blastware_file: frame %d (%s) contains STRT — skipping",
|
"write_blastware_file: frame %d (%s) contains STRT — skipping",
|
||||||
fi, ftype,
|
fi, ftype,
|
||||||
|
|||||||
@@ -126,10 +126,12 @@ DATA_LENGTHS: dict[int, int] = {
|
|||||||
_BULK_CHUNK_OFFSET = 0x1004 # offset field for probe + all regular chunk requests ✅
|
_BULK_CHUNK_OFFSET = 0x1004 # offset field for probe + all regular chunk requests ✅
|
||||||
_BULK_TERM_OFFSET = 0x005A # offset field for termination request ✅
|
_BULK_TERM_OFFSET = 0x005A # offset field for termination request ✅
|
||||||
_BULK_COUNTER_STEP = 0x0400 # chunk counter increment per chunk ✅
|
_BULK_COUNTER_STEP = 0x0400 # chunk counter increment per chunk ✅
|
||||||
# Chunk counter formula: chunk_num * 0x0400 for ALL chunks including chunk 1.
|
# Chunk counter formula: key4[2:4] + (chunk_num - 1) * 0x0400
|
||||||
# Earlier captures showed 0x1004 for chunk 1 — that was a Blastware artifact, not a
|
# where key4[2:4] is the event's circular-buffer base offset ((key4[2]<<8)|key4[3]).
|
||||||
# protocol requirement. Confirmed 2026-04-06: 0x0400 for chunk 1 works; 0x1004
|
# Earlier captures showed 0x1004 for chunk 1 of key 01110000 — that was a Blastware
|
||||||
# causes a 120-second device timeout. Formula n * 0x0400 is used for all chunks.
|
# artifact. For keys where key4[2:4] != 0x0000 (e.g. key 01111884) the old
|
||||||
|
# "n * 0x0400" formula sends counters from the wrong buffer region and the device
|
||||||
|
# returns data from a different event. Confirmed correct 2026-04-24.
|
||||||
|
|
||||||
# Default timeout values (seconds).
|
# Default timeout values (seconds).
|
||||||
# MiniMate Plus is a slow device — keep these generous.
|
# MiniMate Plus is a slow device — keep these generous.
|
||||||
|
|||||||
Reference in New Issue
Block a user