From 77d9c17680220aa1c36b823a797ec608e5c63ec8 Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Sat, 11 Apr 2026 01:15:11 -0400 Subject: [PATCH] docs: update CHANGELOG and CLAUDE.md for v0.9.0 CHANGELOG.md: - New v0.9.0 section covering erase-all protocol, browse helpers, delete_all_events(), ach_mitm.py, and ACH server overhaul - Back-filled v0.8.0 section (write pipeline, monitoring, ACH server) that was missing from the previous release notes CLAUDE.md: - Bump version to v0.9.0 - Add erase-all protocol section with full wire sequence, SUB 0x06 storage range response layout, and post-erase key counter reset notes - Document ACH server state format (ach_state.json v0.9.0 schema with downloaded_keys + max_downloaded_key) - Add RV55 DCD/DTR issue to What's next Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 86 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abb3f5..ca169d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,107 @@ All notable changes to seismo-relay are documented here. --- +## v0.9.0 — 2026-04-11 + +### Added + +- **`MiniMateClient.list_event_keys()`** — fast browse-mode walk (1E → 0A → 1F, no waveform + download) that returns the list of event key hex strings currently stored on the device. + Used by the ACH server as a cheap pre-check before deciding whether to call `get_events()`. + +- **`get_events(skip_waveform_for_keys=set(...))`** — new optional parameter. For any key in + the set the function performs only 0A + 1F(browse) instead of the full + 1E-arm → 0C → POLL×3 → 5A sequence. Eliminates redundant waveform downloads on repeat + call-homes when the device still holds previously downloaded events. + +- **`MiniMateClient.delete_all_events()`** — erases all events from device memory using the + confirmed 4-step sequence: + - SUB 0xA3 `begin_erase_all` — initiate erase (token=0xFE) → ack 0x5C + - SUB 0x1C `read_monitor_status` — intermediate status read (Blastware-required) + - SUB 0x06 `read_event_storage_range` — verify storage state (token=0xFE) → 36-byte response + - SUB 0xA2 `confirm_erase_all` — commit erase (token=0xFE) → ack 0x5D + + All four steps confirmed from 4-11-26 MITM capture of a live Blastware ACH session. + After a successful call, the device's event counter resets to `0x01110000`. + +- **`MiniMateProtocol` erase methods**: `begin_erase_all()`, `confirm_erase_all()`, + `read_event_storage_range()` added to `protocol.py` with documented SUB constants + `SUB_ERASE_ALL_BEGIN = 0xA3` and `SUB_ERASE_ALL_CONFIRM = 0xA2`. + +- **`bridges/ach_mitm.py`** — transparent TCP-to-TCP MITM proxy. Listens for inbound unit + connections, connects upstream to a real Blastware ACH server, and saves both directions + to `raw_bw_.bin` / `raw_s3_.bin` files matching the existing capture format. + Used to capture the 4-11-26 Blastware ACH session including event deletion. + Usage: `python bridges/ach_mitm.py --bw-host 127.0.0.1 --bw-port 9999 --listen-port 9998` + +- **ACH server: key-based state tracking** — `ach_state.json` now stores + `downloaded_keys: [hex_strings]` and `max_downloaded_key: hex_string` per unit instead of + `event_count: N`. This correctly handles the standard workflow where events are deleted + from the device after upload — a count-based approach would see `count=0` on the next + call-home and silently skip new events. + +- **ACH server: `--clear-after-download` flag** — after a successful download (at least one + new event saved), erases all events from the device using `delete_all_events()`. Mirrors + the standard Blastware ACH workflow. On success, `downloaded_keys` and + `max_downloaded_key` are reset to empty so the next session starts fresh. + +- **ACH server: post-erase key-reuse detection** — after an external erase (Blastware or + manual), device keys restart from `0x01110000`, colliding with previously downloaded keys. + On each browse walk, if `max(device_keys) < max_downloaded_key` (device counter rolled + back), all device keys are treated as new regardless of `seen_keys`. This also catches + erases performed by Blastware between our sessions. + +### Protocol / Documentation + +- **SUB 0xA3 / SUB 0xA2 — erase-all sequence confirmed** (✅ 4-11-26 MITM capture): + Both frames use `token=0xFE` at `params[7]` and are standard `build_bw_frame` requests + (not write-format). Response SUBs follow the standard formula: 0x5C and 0x5D. + The intermediate 0x1C + 0x06 reads between them are required by Blastware. + +- **SUB 0x06 — event storage range read confirmed** (✅ 4-11-26 MITM capture): + Two-step read, data offset = 0x24 (36 bytes). The last 8 bytes of the response contain + the first and last stored event keys (4 bytes each). After a successful erase, both keys + read as `01110000` (device-empty state). + +- **Event key counter resets to `0x01110000` after erase** — confirmed by observing key + `01110000` on the device immediately after the MITM erase session. + +--- + +## v0.8.0 — 2026-04-07 + +### Added + +- **Write pipeline end-to-end** — `push_config_raw(event_index_data, compliance_data, + trigger_data, waveform_data)` on `MiniMateClient` orchestrates the full + `68→73 | 71×3→72 | 82→83 | 69→74→72` write sequence. + +- **`build_bw_write_frame(sub, data, *, offset, params)`** in `framing.py` — dedicated frame + builder for write commands (SUBs 0x68–0x83). Doubles only the BW_CMD byte; all other + bytes including offset, params, data, and checksum are written raw. Uses the large-frame + DLE-aware checksum (`sum(b for b in payload[2:] if b != 0x10) + 0x10) & 0xFF`). + +- **`MiniMateProtocol` write methods** — `write_event_index()`, `write_compliance()`, + `write_trigger_config()`, `write_waveform_data()`, `write_confirm()`, + `start_monitoring()`, `stop_monitoring()`. + +- **`AchSession` inbound server** (`bridges/ach_server.py`) — accepts call-home TCP + connections, runs the full handshake + device-info + event-download sequence, saves + `device_info.json` + `events.json` per session. + +### Protocol / Documentation + +- **Write frame format confirmed** (✅ 3-11-26 BW TX capture, all 11 frames): only BW_CMD + byte `0x10` is doubled; all other bytes sent raw. Standard `build_bw_frame` DLE-stuffing + is incorrect for write commands. +- **Write ack responses** confirmed as 17-byte zero-data S3 frames. +- **Monitoring SUBs 0x96/0x97** confirmed from 4-8-26 capture. +- **SESSION_RESET signal** (`41 03`) required before POLL for monitoring units. +- **SUB 0x1C monitoring flag** at `section[1]`: `0x00` = idle, `0x10` = monitoring. + Confirmed by byte-diff of all 144 data frames in 4-8-26/2ndtry capture. + +--- + ## v0.7.0 — 2026-04-03 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index b6faa1c..6dafd73 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem -(Sierra Wireless RV50 / RV55). Current version: **v0.8.0**. +(Sierra Wireless RV50 / RV55). Current version: **v0.9.0**. --- @@ -25,9 +25,9 @@ CHANGELOG.md ← version history --- -## Current implementation state (v0.8.0) +## Current implementation state (v0.9.0) -Full read pipeline + write pipeline working end-to-end over TCP/cellular: +Full read pipeline + write pipeline + erase pipeline working end-to-end over TCP/cellular: | Step | SUB | Status | |---|---|---| @@ -41,12 +41,15 @@ Full read pipeline + write pipeline working end-to-end over TCP/cellular: | Waveform record (peaks, timestamp, project) | 0C | ✅ | | **Bulk waveform stream (event-time metadata)** | **5A** | ✅ new v0.6.0 | | Event advance / next key | 1F | ✅ | -| **Write commands (push config to device)** | **68–83** | ✅ **new v0.8.0** | +| **Write commands (push config to device)** | **68–83** | ✅ new v0.8.0 | +| **Erase all events** | **0xA3 → 0x1C → 0x06 → 0xA2** | ✅ **new v0.9.0** | `get_events()` sequence per event: `1E → 0A → 0C → 5A → 1F` `push_config_raw()` write sequence: `68→73 | 71×3→72 | 82→83 | 69→74→72` +`delete_all_events()` erase sequence: `0xA3 → 0x1C → 0x06 → 0xA2` + --- ## Protocol fundamentals @@ -720,9 +723,82 @@ Full compliance config encoder is a future task. --- +--- + +## Erase-all protocol (SUBs 0xA3/0xA2/0x06) — confirmed 2026-04-11 + +Full sequence confirmed from 4-11-26 MITM capture of a live Blastware ACH session +(`bridges/captures/mitm/ach_mitm_20260411_001912/`). + +### Wire sequence + +``` +BW → device: SUB 0xA3 params=00 00 00 00 00 00 00 FE 00 00 (begin erase) +device → BW: SUB 0x5C (ack) +BW → device: SUB 0x1C probe (offset=0x00) +device → BW: SUB 0xE3 (probe ack) +BW → device: SUB 0x1C data (offset=0x2C) +device → BW: SUB 0xE3 (monitor status response) +BW → device: SUB 0x06 probe (offset=0x00, params same) +device → BW: SUB 0xF9 (probe ack) +BW → device: SUB 0x06 data (offset=0x24) +device → BW: SUB 0xF9 (36-byte storage range response) +BW → device: SUB 0xA2 params=00 00 00 00 00 00 00 FE 00 00 (confirm erase) +device → BW: SUB 0x5D (ack — device memory is now cleared) +``` + +All frames use standard `build_bw_frame` (not write-format). Response SUBs follow the +standard `0xFF - SUB` formula; no exceptions. + +### SUB 0x06 — event storage range response (36 bytes) + +The 36-byte response body ends with two 4-byte event keys: + +| Offset (from end) | Field | Notes | +|---|---|---| +| `[-8:-4]` | first stored event key | `01110000` when empty | +| `[-4:]` | last stored event key | `01110000` when empty | + +Before erase: ends with ` ` (e.g. `0111ea60 0111eaa6`). +After erase: both bytes read `01110000` — device's empty/reset sentinel. + +### Post-erase key counter reset + +After a successful erase, the device resets its event counter. New events start from +key `0x01110000` again — the same key as the very first event ever recorded. This means +key-based deduplication in the ACH server must account for key reuse: + +- After our own erase: `ach_state.json` `downloaded_keys` and `max_downloaded_key` are + cleared so the next session starts fresh. +- After an external erase: the ACH server detects it by comparing `max(device_keys)` to + `max_downloaded_key` from state. If the device max has rolled back below the historical + max, all current device keys are treated as new regardless of `seen_keys`. + +### ACH server state format (v0.9.0) + +`bridges/captures/ach_state.json`: +```json +{ + "BE11529": { + "downloaded_keys": ["01110000", "0111245a"], + "max_downloaded_key": "0111245a", + "last_seen": "2026-04-11T01:04:36", + "serial": "BE11529", + "peer": "63.43.212.232:51920" + } +} +``` + +`max_downloaded_key` is the high-water mark — the largest key ever downloaded from the +unit. It is NOT reset when events are erased from the device (only when our server does +the erase). Used for post-erase detection. + +--- + ## What's next - Compliance config encoder — build raw write payloads from a `ComplianceConfig` object - Locate "Sensor Check" byte in compliance config (need capture with Disabled vs Before-monitoring) -- ACH inbound server — accept call-home connections from field units - Modem manager — push RV50/RV55 configs via Sierra Wireless API +- RV55 DCD/DTR issue — newer RV55 firmware doesn't assert DCD by default; units don't + resume monitoring after call-home disconnect (`--restart-monitoring` flag deferred)