doc: update to .0.6.0 with full working event read loop

This commit is contained in:
Brian Harrison
2026-04-02 17:30:33 -04:00
parent 0f5aa7a3fc
commit 5d0f0855f2
3 changed files with 151 additions and 16 deletions

View File

@@ -72,6 +72,11 @@
| 2026-04-01 | §7.6.1 | **CORRECTED — Record time offset.** Previous doc (`+0x28` from E5 data page2 start) was correct for single-frame reads but unreliable for BE11529 due to a 1-byte DLE jitter (see §7.6.3). The `minimateplus` library now uses an anchor-based approach: search for `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` in cfg[40:100]; record time float32 BE is at anchor+10. Validated at 3.0, 5.0, and 8.0 seconds. |
| 2026-04-01 | §7.6.3 (NEW) | **NEW — Sample rate confirmed and documented.** Sample rate (Normal=1024 / Fast=2048 / Faster=4096 Sa/s) is stored as uint16 BE at anchor2, where anchor is the 10-byte sequence `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00`. DLE jitter root cause: 4096 = 0x1000, so in the raw S3 frame the sample-rate bytes are sent as `10 10 00` (DLE-escaped `10`); after DLE unstuffing → `10 00` (2 bytes instead of 3 for 1024/2048), making frame C 1 byte shorter and shifting all subsequent offsets by 1. Anchor search is immune to this shift. All three modes confirmed on BE11529 firmware S338.17. |
| 2026-04-01 | §5.1 | **CONFIRMED — `_pending_frames` buffer and `reset_parser=False` parameter.** `MiniMateProtocol._recv_one()` now supports `reset_parser=False` to preserve parser state between consecutive reads within a multi-frame sequence. A `_pending_frames: list[S3Frame]` buffer stores extra frames parsed from a single TCP chunk when multiple E5 responses arrive together. Required for reliable SUB 1A frame B/C/D sequence on BE11529. |
| 2026-04-02 | §7.8 (NEW) | **CONFIRMED — SUB 5A frame format.** `offset_hi` byte (`0x10`) must be sent **raw, not DLE-stuffed** — standard `build_bw_frame` incorrectly stuffs it to `10 10` on the wire; device ignores the frame. BW sends it as bare `10`. Checksum is **DLE-aware**: when walking the byte sequence, `10 XX` pairs contribute only `XX` to the sum; lone bytes contribute normally. `build_5a_frame()` reproduces BW's exact wire format. |
| 2026-04-02 | §7.8 | **CONFIRMED — SUB 5A params are 11 bytes (not 10) for chunk frames.** Extra trailing `0x00` confirmed from 1-2-26 BW wire capture. Probe frame and termination frame differ — see `bulk_waveform_params()` in `framing.py`. |
| 2026-04-02 | §7.7.5 | **CONFIRMED — Event-time metadata source.** `Client:`, `User Name:`, and `Seis Loc:` strings are present in **A5 frame 7** of the SUB 5A bulk waveform stream — they are NOT in the 210-byte SUB 0C waveform record. They reflect the compliance setup active when the event was stored on the device (not the current setup). `get_events()` now issues SUB 5A after each 0C download. Sequence: `1E → 0A → 0C → 5A → 1F`. |
| 2026-04-02 | §7.6.2 | **FIXED — Compliance config orphaned send bug.** An extra `self._send(SUB_COMPLIANCE / 0x2A / DATA_PARAMS)` before the B/C/D receive loop had no corresponding `recv_one()`. Every receive in the loop was consuming the previous send's response, leaving frame D's channel block unread. Bug removed. Total config bytes now ~2126 (was ~1071 due to truncation). `trigger_level_geo`, `alarm_level_geo`, `max_range_geo` are now correctly populated. |
| 2026-04-02 | §7.6.1 | **CORRECTED — Anchor search range.** Previous doc stated anchor search range `cfg[40:100]`. With the orphaned-send bug fixed, the 44-byte header padding is gone and the anchor now appears at `cfg[11]`. Corrected to `cfg[0:150]`. |
---
@@ -495,7 +500,9 @@ Values are stored natively in **imperial units (in/s)** — unit strings `"in."`
Record time is stored as a **32-bit IEEE 754 float, big-endian**, located via an anchor pattern (see §7.6.3 below).
**Anchor-relative location:** search for the 10-byte sequence `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` in cfg[40:100]. Record time float is at **anchor + 10**.
**Anchor-relative location:** search for the 10-byte sequence `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` in `cfg[0:150]`. Record time float is at **anchor + 10**.
> ✅ **2026-04-02 — CORRECTED:** Search range was `cfg[40:100]`. With the compliance-config orphaned-send bug fixed (§7.6.2), the 44-byte accidental header padding is gone and the anchor now appears at `cfg[11]`. Search range widened to `cfg[0:150]`.
| Record Time | float32 BE bytes | Decoded |
|---|---|---|
@@ -907,16 +914,20 @@ Near-ambient: 0x3C75C28F = 0.015 in/s (histogram event, near-zero ambient)
**Project strings** — ASCII label-value pairs (search for label, read null-terminated value):
```
"Project:" → project description (present in 0C record ✅)
"Client:" → client name (NOT in 0C; comes from compliance config SUB 1A/E5 ❓)
"User Name:" → operator / user (NOT confirmed in 0C)
"Seis Loc:" → sensor location (NOT confirmed in 0C)
"Extended Notes"→ notes field
"Project:" → project description (in 0C record ✅)
"Client:" → client name (in SUB 5A / A5 frame 7 ✅ — NOT in 0C)
"User Name:" → operator / user (in SUB 5A / A5 frame 7 ✅ — NOT in 0C)
"Seis Loc:" → sensor location (in SUB 5A / A5 frame 7 ✅ — NOT in 0C)
"Extended Notes"→ notes field (in SUB 5A / A5 frame 7 ✅)
```
> **Clarification needed:** Only "Project:" has been confirmed in the 210-byte 0C record.
> "Client:", "User Name:", and "Seis Loc:" appear in the Blastware event report but their
> source in the protocol (0C vs SUB 1A/E5 compliance config) is not yet confirmed.
> **2026-04-02 — CONFIRMED:** `Client:`, `User Name:`, and `Seis Loc:` are sourced from
> **SUB 5A (bulk waveform stream)**, specifically A5 frame 7 of the multi-frame response.
> They are NOT present in the 210-byte SUB 0C waveform record. The strings reflect the
> compliance setup that was active when the event was recorded on the device — making SUB 5A
> the authoritative source for true event-time metadata. The `get_events()` client method
> now issues a SUB 5A request after each 0C download (`stop_after_metadata=True`) and
> overwrites `event.project_info` with the decoded fields.
---
@@ -948,6 +959,101 @@ return events
---
### 7.7.7 Updated Download Loop with SUB 5A Metadata
> ✅ **Added 2026-04-02.** Confirmed working on BE11529 over TCP/cellular.
```python
key4, _ = proto.read_event_first() # SUB 1E
if key4 == b'\x00\x00\x00\x00':
return []
events = []
is_first = True
while key4 != b'\x00\x00\x00\x00':
if is_first:
_header, rec_len = proto.read_waveform_header(key4) # SUB 0A
is_first = False
if rec_len < 0x30:
key4 = proto.advance_event()
continue
record = proto.read_waveform_record(key4) # SUB 0C (0xD2 bytes)
event = decode(record)
a5_data = proto.read_bulk_waveform_stream( # SUB 5A → A5 frames
key4, stop_after_metadata=True)
client._decode_a5_metadata_into(a5_data, event) # overwrites project_info
events.append(event)
key4 = proto.advance_event() # SUB 1F (token=0xFE)
return events
```
---
### 7.8 SUB 5A — Bulk Waveform Stream (event-time metadata)
> ✅ **Added 2026-04-02.** Frame format confirmed by reproducing Blastware wire bytes
> byte-for-byte from the 1-2-26 BW capture.
SUB 5A initiates a bulk transfer of the raw sample data for a stored event. The response is a
sequence of A5 frames. Frame 7 (0-indexed) contains the full compliance setup as it existed
when the event was recorded — including `Client:`, `User Name:`, `Seis Loc:`, and
`Extended Notes` ASCII label-value pairs.
#### 7.8.1 Frame Format
SUB 5A uses a **non-standard frame layout** that differs from all other BW→S3 write commands.
```
[ACK][STX][10][10][00][5A][00][offset_hi][offset_lo][params...][chk][ETX]
41 02 10 10 00 5A 00 ^^raw^^ ^^raw^^ ^^stuffed^^
```
Two critical differences from `build_bw_frame`:
1. **`offset_hi` is sent raw, not DLE-stuffed.** When `offset_hi = 0x10`, the wire carries
a bare `0x10` — NOT the stuffed `10 10` that `build_bw_frame` would produce. The device
ignores frames where this byte is incorrectly stuffed.
2. **DLE-aware checksum.** Walking the full frame byte sequence: when a `10 XX` pair is seen,
only `XX` is added to the running sum; lone bytes are added normally.
#### 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 |
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 79 chunks. A termination frame
is always sent before returning.
#### 7.8.3 A5 Frame Layout
Each A5 response frame contains a chunk of raw bulk data. Frame 7 of the stream carries the
compliance text block with all project-info label-value pairs. The `client` layer searches
for ASCII labels with a null-terminated value read:
```
"Project:" → null-terminated project name
"Client:" → null-terminated client name
"User Name:" → null-terminated operator name
"Seis Loc:" → null-terminated sensor location
"Extended Notes" → null-terminated notes
```
All five fields reflect the **setup at event-record time**, not the current device config.
---
## 8. Timestamp Format
Two timestamp wire formats are used: