docs(series4): open the Micromate live-protocol reference
First bench session against a Micromate over USB. The headline: the unit answers Series III command frames unmodified. An untouched Series III POLL (SUB 0x5B), built by build_bw_frame with no changes, completed a full two-step probe/data cycle. Ten Series III read commands were then tried and all ten answered, every one obeying the response_SUB = 0xFF - request_SUB rule. Confirmed this session: - Transport is a plain USB CDC-ACM port (2504:0300, "MICROMATE COM PORT"). No vendor driver, no Thor, no Windows box needed. Baud is ignored over USB (identical responses at 38400 and 115200). - Device never speaks first -- 20 s idle listen produced nothing. - Responses are Series III framing MINUS the leading DLE: bare [STX][payload][chk][ETX]. This alone means Blastware can never find a frame boundary in Micromate traffic, since its parser scans for DLE+STX. - Response flags byte is 0xC5, not Series III's 0x10. - Checksum is the DLE-aware variant (SUM8 excluding 0x10 bytes) -- the same one Series III uses for 5A and write frames, not the plain SUM8 of its ordinary reads. Disambiguated by the POLL data frame, which contains a 0x10. - The probe response carries the data length at payload[9]. Four of four known Series III lengths match; call-home config differs (0x7E vs 0x7C). - Series III monitor-status field offsets apply unchanged: battery 3.81 V (Thor's own reports say 3.8), memory 15,000,000 total and free, date 23 Sep 2026. - SUB 0x2C carries the string "RADIO RING" -- the same string seen in the RV50 ALEOS debug during the BE12599 incident. That block holds the modem dial/answer strings and is the most relevant command to the call-home goal. Read commands only. Nothing that writes, erases, or changes monitoring state has been sent to a unit; those are listed as unsafe-until-agreed. Caveat recorded in the doc: one unit, over USB, with zero events stored, so the event-walk commands (0x08, 0x1E, 0x0A, 0x06) could only be probed, not exercised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
# Micromate Protocol Reference — Thor / Micromate Series IV, live wire protocol
|
||||
|
||||
Sibling to [instantel_protocol_reference.md](instantel_protocol_reference.md)
|
||||
(Series III, "the Rosetta Stone") and
|
||||
[idf_protocol_reference.md](idf_protocol_reference.md) (Series IV *file*
|
||||
format). This document covers the Series IV **live device protocol** — what
|
||||
the unit says over the wire, as opposed to what it writes into a `.IDFW`.
|
||||
|
||||
**Status (2026-09-23): opening session.** Everything below was established in
|
||||
a single bench session against one unit. Treat it as a strong start, not a
|
||||
settled spec — in particular, everything here comes from **one unit, over USB,
|
||||
with no events stored**.
|
||||
|
||||
---
|
||||
|
||||
## The headline
|
||||
|
||||
**The Micromate answers Series III command frames.**
|
||||
|
||||
An unmodified Series III `POLL` (`SUB 0x5B`), built by
|
||||
`minimateplus.framing.build_bw_frame` with no changes at all, produced a
|
||||
complete two-step probe/data cycle from a Micromate. Ten Series III read
|
||||
commands were then tried and **all ten answered**, every one obeying the
|
||||
Series III response-SUB rule.
|
||||
|
||||
This is not a new protocol. It is close enough to Series III that large parts
|
||||
of `minimateplus/protocol.py` may apply more or less directly, and the work
|
||||
ahead is mapping *divergence* rather than mapping from scratch.
|
||||
|
||||
---
|
||||
|
||||
## How this was obtained
|
||||
|
||||
No Thor, no modem, no Windows machine. The Micromate exposes its protocol on
|
||||
a **USB CDC-ACM virtual serial port**:
|
||||
|
||||
```
|
||||
ID 2504:0300 Instantel Inc. MICROMATE COM PORT
|
||||
driver: cdc_acm ATTRS{serial}=="V1.00"
|
||||
→ /dev/ttyACM0
|
||||
```
|
||||
|
||||
Plain CDC ACM, so no vendor driver and no proprietary USB layer — any host
|
||||
that can open a serial port can talk to the unit.
|
||||
|
||||
**Baud is irrelevant over USB.** Identical byte-for-byte responses at 38400
|
||||
and 115200; CDC-ACM ignores the line rate. TMI provisions Micromate *modem*
|
||||
links at **115200** (Series III uses 38400) — that matters for the cellular
|
||||
path, not for USB.
|
||||
|
||||
**The device never speaks first.** 20 s of passive listening on an idle open
|
||||
port produced zero bytes. It is strictly request/response.
|
||||
|
||||
---
|
||||
|
||||
## Physical + framing layer
|
||||
|
||||
### Requests — Series III format, unmodified
|
||||
|
||||
Every frame in this session was produced by `build_bw_frame(sub, offset)` with
|
||||
no Series IV changes, and the device accepted all of them:
|
||||
|
||||
```
|
||||
[ACK 0x41] [STX 0x02] [10 10] [flags 00] [SUB] [00] [00] [offset] [params×10] [chk] [ETX 0x03]
|
||||
```
|
||||
|
||||
⚠ Only the doubled `BW_CMD` (`10 10`) form has been exercised. Whether other
|
||||
literal `0x10` bytes inside params require stuffing is **untested** — none of
|
||||
the probes sent carried one.
|
||||
|
||||
### Responses — Series III *minus the DLE prefix*
|
||||
|
||||
```
|
||||
Series III: [DLE 0x10] [STX 0x02] … [chk] [ETX 0x03]
|
||||
Micromate: [STX 0x02] … [chk] [ETX 0x03] ← no leading DLE
|
||||
```
|
||||
|
||||
This single byte matters operationally: Blastware's parser locates frames by
|
||||
scanning for `DLE+STX`, so it will **never find a frame boundary** in Micromate
|
||||
traffic no matter what else is correct. That is a structural reason a
|
||||
Micromate cannot call into a Blastware ACH server, independent of any baud
|
||||
mismatch.
|
||||
|
||||
### Response payload header
|
||||
|
||||
```
|
||||
[0] CMD 0x00 same as Series III
|
||||
[1] flags 0xC5 ← Series III uses 0x10. Constant across all 10 SUBs.
|
||||
[2] SUB 0xFF − request_SUB
|
||||
[3] PAGE_HI
|
||||
[4] PAGE_LO
|
||||
[5+] data
|
||||
```
|
||||
|
||||
### Checksum — the DLE-aware variant
|
||||
|
||||
```python
|
||||
chk = sum(b for b in payload if b != 0x10) & 0xFF
|
||||
```
|
||||
|
||||
Confirmed on every frame captured. The `POLL` probe response contains no
|
||||
`0x10` and so cannot distinguish plain SUM8 from the DLE-aware form; the
|
||||
`POLL` **data** response contains a `0x10` at payload offset 42, and only the
|
||||
DLE-aware rule matches there. This is the same checksum Series III uses for
|
||||
its `5A` bulk-stream and write frames — not the plain SUM8 of ordinary
|
||||
Series III reads.
|
||||
|
||||
### The probe response carries the data length
|
||||
|
||||
Series III hardcodes `DATA_LENGTHS` per SUB. On the Micromate the **probe
|
||||
response tells you**, at `payload[9]`:
|
||||
|
||||
| SUB | command | `payload[9]` | Series III constant |
|
||||
|---|---|---|---|
|
||||
| `0x15` | serial number | `0x0A` | `0x0A` ✓ |
|
||||
| `0x01` | device info | `0x98` | `0x98` ✓ |
|
||||
| `0x1C` | monitor status | `0x2C` | `0x2C` ✓ |
|
||||
| `0x06` | storage range | `0x24` | `0x24` ✓ |
|
||||
| `0x2C` | call-home config | `0x7E` | `0x7C` ✗ **differs by 2** |
|
||||
| `0x08` | event index | `0x5A` | — |
|
||||
| `0x1E` | event header | `0x08` | — |
|
||||
| `0x1A` | compliance config | `0x2C` | — |
|
||||
| `0x0A` | waveform header | `0x00` | — (no event context) |
|
||||
| `0xFE` | full config | `0x00` | — (see note) |
|
||||
|
||||
Four of four known Series III lengths match exactly. **Read the length from
|
||||
the probe rather than hardcoding it** — it is free, and it already caught the
|
||||
call-home divergence.
|
||||
|
||||
---
|
||||
|
||||
## Confirmed commands (read-only)
|
||||
|
||||
All ten below answered with a correct `0xFF − SUB` response. Nothing that
|
||||
writes, erases, or changes monitoring state has been sent to a unit.
|
||||
|
||||
| SUB | RSP | Command | Data proven |
|
||||
|---|---|---|---|
|
||||
| `0x5B` | `0xA4` | POLL / handshake | yes — ID block |
|
||||
| `0x15` | `0xEA` | Serial number | yes — `UM12947` |
|
||||
| `0x01` | `0xFE` | Device info | yes — 152 B |
|
||||
| `0x2C` | `0xD3` | Call-home config | yes — 126 B |
|
||||
| `0x1C` | `0xE3` | Monitor status | yes — 44 B |
|
||||
| `0x06` | `0xF9` | Event storage range | yes — 36 B |
|
||||
| `0x08` | `0xF7` | Event index | probe only |
|
||||
| `0x1E` | `0xE1` | Event header / first key | probe only |
|
||||
| `0x0A` | `0xF5` | Waveform header | probe only |
|
||||
| `0x1A` | `0xE5` | Compliance config | probe only |
|
||||
|
||||
### Decoded so far
|
||||
|
||||
**`SUB 0x15` — serial.** ASCII, null-terminated: `UM12947`.
|
||||
|
||||
**`SUB 0x5B` / `0x01` — identification strings.**
|
||||
`Instantel\0` and `MM/ISEE/S/IO` (MicroMate / ISEE standard). `0x01` also
|
||||
carries eight consecutive `3f 80 00 00` float32 values (= `1.0f`) — almost
|
||||
certainly per-channel calibration/scale factors, by analogy with Series III's
|
||||
`geo_hardware_constant`. **Unverified.**
|
||||
|
||||
**`SUB 0x1C` — monitor status. Series III field offsets apply unchanged:**
|
||||
|
||||
| field | offset | read |
|
||||
|---|---|---|
|
||||
| battery × 100 | `payload[-10:-8]` uint16 BE | `0x017D` → **3.81 V** |
|
||||
| memory total | `payload[-8:-4]` uint32 BE | 15,000,000 |
|
||||
| memory free | `payload[-4:]` uint32 BE | 15,000,000 (empty) |
|
||||
| date | `payload[18:22]` | day 23, month 9, year `0x07EA` = 2026 |
|
||||
|
||||
The battery reading independently corroborates: Thor's own event reports for
|
||||
these units print `BatteryLevel : 3.8 volts`.
|
||||
|
||||
**`SUB 0x2C` — call-home config.** Contains the ASCII string `RADIO RING`.
|
||||
Worth flagging: that is the *exact* string seen in the RV50 `ALEOS_SERIAL`
|
||||
debug during the BE12599 incident —
|
||||
`'ATQ1^MATE0^MATS0=2^M^MRADIO RING^M'`. So this block holds the modem dial /
|
||||
answer strings, and it is the most directly relevant command to the
|
||||
call-home-receiver goal. Field layout **not yet mapped**; Series III's map
|
||||
(`raw[5]` enabled, `raw[6:46]` dial string) is a starting hypothesis only, and
|
||||
the length already differs (`0x7E` vs `0x7C`).
|
||||
|
||||
**`SUB 0x06` — storage range.** All zeros on this unit, consistent with
|
||||
`memory free == memory total`. Series III reads first/last event keys from
|
||||
the final 8 bytes; untestable until the unit holds events.
|
||||
|
||||
---
|
||||
|
||||
## Divergences from Series III (running list)
|
||||
|
||||
1. **No `DLE` prefix on responses** — bare `STX`.
|
||||
2. **Response flags byte is `0xC5`**, not `0x10`.
|
||||
3. **Call-home config is 126 bytes**, not 124.
|
||||
4. **Data lengths are discoverable** from the probe response at `payload[9]`.
|
||||
5. **Modem serial rate is 115200**, not 38400 (per TMI provisioning practice;
|
||||
not independently verified here).
|
||||
|
||||
---
|
||||
|
||||
## ⚠ Untested and unsafe-until-agreed
|
||||
|
||||
Nothing below has been sent to a unit, and nothing should be without an
|
||||
explicit decision:
|
||||
|
||||
- **Writes** (`0x68`–`0x83`), **call-home write** (`0x7E`/`0x7F`)
|
||||
- **Erase** (`0xA3` / `0xA2`)
|
||||
- **Start / stop monitoring** (`0x96` / `0x97`)
|
||||
- `0x1F` (advance event pointer) — non-destructive on Series III but it does
|
||||
move device state, so it is parked with the rest
|
||||
|
||||
Also unknown:
|
||||
|
||||
- Whether `0x10` bytes inside request params need stuffing
|
||||
- Whether the bulk waveform stream (`5A` on Series III) exists here, and
|
||||
whether it is the transport for `.IDFW` bodies we already decode
|
||||
- Everything about the **call-home session** — the device-initiated direction
|
||||
has not been observed at all. Specifically: how a unit announces itself,
|
||||
and **how it learns an event was accepted so it stops re-sending it.**
|
||||
That last question gates any homebrew receiver and cannot be answered over
|
||||
USB.
|
||||
|
||||
---
|
||||
|
||||
## Session provenance
|
||||
|
||||
Unit **UM12947**, on the bench via USB, **zero events stored** (memory free ==
|
||||
total). Read commands only. Every response in this document was checksum-
|
||||
validated.
|
||||
|
||||
An empty unit is a real limitation: `0x08`, `0x1E`, `0x0A` and `0x06` all have
|
||||
event-dependent payloads that could not be exercised. Recording a couple of
|
||||
events on the bench unit would unlock the entire event-walk half of the
|
||||
protocol.
|
||||
Reference in New Issue
Block a user