From 5b3e8af1e37400795aa45f66e297aef08e90740d Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Mon, 6 Apr 2026 17:03:09 -0400 Subject: [PATCH] fix: remove special case chunk counter, all chunks use chunk_num * 0x0400 --- CLAUDE.md | 16 ++++++++++++++++ docs/instantel_protocol_reference.md | 26 +++++++++++++++++++------- minimateplus/protocol.py | 10 ++++++---- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ec524e6..33651db 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,6 +109,22 @@ S3→BW (response): Both differences confirmed by reproducing Blastware's exact wire bytes from the 1-2-26 BW TX capture. All 10 frames verified. +### SUB 5A — chunk counter is monotonic (CORRECTED 2026-04-06) + +**Chunk counters are `chunk_num * 0x0400` for ALL chunks including chunk 1.** + +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 +artifact, not a protocol requirement. Empirical test 2026-04-06: with `counter=0x1004` for +chunk 1 the device times out (120 s); with `counter=0x0400` (= `1 * 0x0400`) it responds +immediately and streams all frames correctly. + +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 +true formula is `key4[2:4] + n * 0x0400` — but since `key4[2:4]` of the first event is +`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. + ### SUB 5A — params are 11 bytes for chunk frames, 10 for termination `bulk_waveform_params()` returns 11 bytes (extra trailing `0x00`). The 11th byte was diff --git a/docs/instantel_protocol_reference.md b/docs/instantel_protocol_reference.md index 719a80e..dd54da5 100644 --- a/docs/instantel_protocol_reference.md +++ b/docs/instantel_protocol_reference.md @@ -88,6 +88,7 @@ | 2026-04-06 | §6.1 | **CONFIRMED — SUB 1F(token=0xFE) must precede POLL×3 before 5A.** BW always sends 1F(0xFE) before the 3 POLL cycles before 5A. 5A still uses the pre-advance key (set by 0A+1E-arm+0C); 1F only arms the device's 5A state machine. | | 2026-04-06 | §6.1 | **CONFIRMED — browse 1F must be conditional.** Calling 1F(browse=True/all-zero) after a FAILED 5A disrupts device state and causes the next event's 5A probe to time out with 0 bytes received. Browse 1F is only called after a SUCCESSFUL 5A. Failure fallback: use the key returned by the prior 1F(arm/0xFE) call. | | 2026-04-06 | §7.8 | **ADDED — `bytes_fed` diagnostic counter on S3FrameParser.** Counts raw bytes fed to the parser since last `reset()`. Logged at WARNING when 5A probe times out — distinguishes "device sent no bytes at all" from "device responded but frame was malformed or had wrong SUB". | +| 2026-04-06 | §7.8.2 | **CORRECTED — SUB 5A chunk counter is monotonic for ALL chunks.** Previous doc hard-coded chunk 1 counter as `0x1004` (from 4-2-26 BW TX capture). This was a Blastware artifact. Empirically confirmed: `counter = chunk_num * 0x0400` works (device responds immediately); counter=0x1004 for chunk 1 causes 120 s timeout. BW's true internal formula appears to be `key4[2:4] + n * 0x0400` — for event 1 (key `01110000`) this equals `n * 0x0400`. The device does not strictly validate counter values. | --- @@ -1140,13 +1141,24 @@ Two critical differences from `build_bw_frame`: #### 7.8.2 Request Sequence -| Frame | offset_word | params | Purpose | -|---|---|---|---| -| Probe | `0x1004` | 10 bytes (`bulk_waveform_params(0)`) | Initiate transfer | -| Chunk 1 | `0x1004` | 11 bytes (`bulk_waveform_params(counter)`) | First data chunk | -| Chunk 2 | `0x1004` | 11 bytes, counter += `0x0400` | Second chunk | -| … | … | … | … | -| Termination | `0x005A` | 11 bytes, term_counter = last+`0x0400` | End transfer | +| Frame | offset_word | counter | params | Purpose | +|---|---|---|---|---| +| Probe | `0x1004` | `0x0000` | 10 bytes (`bulk_waveform_params(0)`) | Initiate transfer | +| Chunk 1 | `0x1004` | `0x0400` | 11 bytes | First data chunk | +| Chunk 2 | `0x1004` | `0x0800` | 11 bytes | Second chunk | +| Chunk N | `0x1004` | `N * 0x0400` | 11 bytes | Nth chunk | +| … | … | … | … | … | +| Termination | `0x005A` | `last + 0x0400` | 10 bytes | End transfer | + +> ⚠️ **2026-04-06 CORRECTED — chunk counter is monotonic for ALL chunks.** +> The 4-2-26 BW TX capture showed counter=0x1004 for chunk 1, which was hardcoded as a +> special case. This was a Blastware artifact. Empirically confirmed: counter=0x0400 for +> chunk 1 works correctly; counter=0x1004 causes the device to time out. The device does +> 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. +> BW's true internal formula is `key4[2:4] + n * 0x0400`. For event 1 (key `01110000`) +> this equals `n * 0x0400` since `key4[2:4] = 0x0000`. The monotonic formula is correct +> for all keys encountered on this device. 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 7–9 chunks. A termination frame diff --git a/minimateplus/protocol.py b/minimateplus/protocol.py index d2b8208..c6aa338 100644 --- a/minimateplus/protocol.py +++ b/minimateplus/protocol.py @@ -508,11 +508,13 @@ class MiniMateProtocol: log.debug("5A A5[0] page_key=0x%04X %d bytes", rsp.page_key, len(rsp.data)) # ── Step 2: chunk loop ─────────────────────────────────────────────── - # Chunk 1 uses a fixed counter of 0x1004, confirmed from 4-2-26 BW TX capture. - # Chunks 2+ use n * 0x0400. Device silently ignores frames with wrong counter. - _CHUNK1_COUNTER = 0x1004 + # Chunk counters are monotonic: chunk_num * 0x0400 for all chunks. + # The 4-2-26 BW TX capture showed 0x1004 for chunk 1, but this is a + # Blastware artifact — the device accepts any counter value and streams + # data regardless. Empirically confirmed 2026-04-06: 0x0400 for chunk 1 + # works; 0x1004 causes the device to ignore the frame (timeout). for chunk_num in range(1, max_chunks + 1): - counter = _CHUNK1_COUNTER if chunk_num == 1 else chunk_num * _BULK_COUNTER_STEP + counter = chunk_num * _BULK_COUNTER_STEP params = bulk_waveform_params(key4, counter) log.debug("5A chunk %d counter=0x%04X", chunk_num, counter) self._send(build_5a_frame(_BULK_CHUNK_OFFSET, params))