Five events on the bench unit (4 waveform + 1 histogram). The Series III browse walk -- 1E, then 0A/0C per key, then 1F to advance -- works unmodified, and the null sentinel terminated correctly after exactly 5. Findings: - Event keys are a sequential counter (055d4a81..85), NOT flash-buffer addresses. Series III key arithmetic does not carry over; its 5A chunk walk assumes addresses and must not be ported blindly. - The 4 bytes after the key in 1E/1F are the event's SIZE in bytes, where Series III puts an offset to the next key. 4,076 for the histogram and 8.7-13.4 KB for the waveforms, matching real .IDFH/.IDFW file sizes. - SUB 0x0C returns a 210-byte (0xD2) waveform record -- the same length as Series III -- carrying the event key, date/time, the title note "Location", the PROJECT STRING, the serial, channel labels Tran/Vert/Long/Mic and float32 peaks. That last point closes the biggest open question for the call-home receiver: the job identity strings that today arrive only via Thor's .txt sidecar, and which no amount of sample decoding can reconstruct, are readable over the wire. Direct-to-SFM events need not arrive with blank metadata. - SUB 0x0A returns len 0x1E for the histogram and 0x00 for every waveform. The histogram payload holds two timestamps plus a "Vert: 0.300 in/s" trigger string -- structurally the Series III monitor-log partial record. So 0A describes interval records and 0C describes triggered events; Series III's 0x46-vs-0x2C length discriminator does not apply. - DLE stuffing in responses is now confirmed (previously marked untested): the 0C timestamp contains 10 10, which destuffs to one 0x10 and yields a clock reading of 16:33 on 23 Sep 2026 -- matching when the events were recorded. Read-only throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
386 lines
16 KiB
Markdown
386 lines
16 KiB
Markdown
# 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
|
||
|
||
**A Micromate running the *Blastware* firmware 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. Ten Series III read commands were then
|
||
tried and **all ten answered**, every one obeying the Series III response-SUB
|
||
rule.
|
||
|
||
⚠ **That qualifier is load-bearing, and it was discovered after the fact.**
|
||
Instantel ships the Micromate in two firmware lines:
|
||
|
||
| firmware | Instantel's own description |
|
||
|---|---|
|
||
| `11.0CB` | "Utilize with **Blastware**" |
|
||
| `11.0BD` | "Utilize with **THOR, Vision, Vision II**" |
|
||
|
||
The bench unit reports **`11.0CB`** — the Blastware build. So the clean
|
||
Series III behaviour above is very likely *because the unit is in Blastware
|
||
mode*, not because the Micromate natively speaks Series III. **Nothing here
|
||
should be assumed to hold on a `11.0BD` unit until tested.**
|
||
|
||
This reframes the project. The question is no longer only "what is the
|
||
Series IV protocol" but **"which firmware line do we target, and does one of
|
||
them let the existing Series III stack drive the whole fleet?"**
|
||
|
||
---
|
||
|
||
## Firmware — the variable nobody knew was a variable
|
||
|
||
This explains a production problem TMI has lived with: two ACH servers, two
|
||
machines, and units that will not cross over. It is not a misconfiguration.
|
||
**Instantel ships different firmware for different host software**, and the
|
||
wire protocol differs with it.
|
||
|
||
Versions observed so far:
|
||
|
||
| where | version | notes |
|
||
|---|---|---|
|
||
| bench unit UM12947, today | **`11.0CB`** | Blastware line — answers Series III |
|
||
| corpus, 932 event files | `11.0AK` | produced Thor-collected `.IDFW`/`.IDFH` |
|
||
| corpus, 83 event files | `10.90GC` | older; UM12947's own files from Sept 2025 |
|
||
|
||
Note UM12947 produced `10.90GC` files in production last year and reports
|
||
`11.0CB` on the bench now — so **it has been reflashed at some point**, and
|
||
firmware is not stable per-unit over time. Any fleet-wide claim needs a
|
||
per-unit firmware audit first.
|
||
|
||
The version is readable over the wire from `SUB 0x01` as two separate ASCII
|
||
runs — `"0CB"` then `"11"` — with no single concatenated string.
|
||
|
||
### The strategic fork
|
||
|
||
- **Option A — standardise the fleet on the Blastware line.** Every unit,
|
||
MiniMate and Micromate alike, then speaks Series III, and the existing
|
||
`minimateplus/` stack drives all of it. One protocol, one call-home
|
||
receiver. Dramatically cheaper *if* it holds up.
|
||
- **Option B — reverse-engineer the Thor line (`11.0BD`) and support both.**
|
||
|
||
Option A is the shortcut, but it is unproven and carries real unknowns, all
|
||
of which are cheap to answer on the bench and expensive to discover later:
|
||
|
||
1. **What file format does a `11.0CB` unit produce?** If it emits Blastware
|
||
binaries rather than `.IDFW`/`.IDFH`, the (now exact) Series III decoder
|
||
applies and the IDF codec becomes a legacy path. Not a loss — we have
|
||
both — but it changes what the ingest pipeline sees.
|
||
2. **Does Thor still work with a `11.0CB` unit?** If not, flashing a
|
||
production unit breaks data collection until the replacement path exists.
|
||
3. **Are any Micromate-specific capabilities lost** on the Blastware line?
|
||
4. **Is the flash reversible in the field**, and what does it cost in downtime?
|
||
|
||
## 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.
|
||
|
||
---
|
||
|
||
## The event chain — walked end to end (2026-09-23, 5 events)
|
||
|
||
With 5 events on the bench unit (4 waveform + 1 histogram), the Series III
|
||
browse walk works unmodified:
|
||
|
||
```
|
||
1E (all-zero params) -> first key + size
|
||
0A (key) -> partial record, histogram only
|
||
0C (key) -> 210-byte waveform record
|
||
1F (all-zero params/browse) -> next key + size
|
||
... repeat ...
|
||
1F -> all-zero key = NULL SENTINEL, chain ends
|
||
```
|
||
|
||
The sentinel terminated correctly after exactly 5 events.
|
||
|
||
### Event keys are sequential, not addresses
|
||
|
||
```
|
||
055d4a81 055d4a82 055d4a83 055d4a84 055d4a85
|
||
```
|
||
|
||
**This is a real divergence.** Series III keys are flash-buffer *addresses*
|
||
(`01110000`, `011121F2`, …) that advance by the event's byte length, which is
|
||
why its 5A chunk walk is address-arithmetic. Micromate keys are a plain
|
||
incrementing counter. Any port of the Series III download walk must not
|
||
assume key arithmetic means anything.
|
||
|
||
### The 4 bytes after the key are the event's size
|
||
|
||
`1E`/`1F` return `[key 4B][size 4B]`. Series III uses that slot as an offset
|
||
to the next key; here it is a byte count:
|
||
|
||
| key | size | kind |
|
||
|---|---|---|
|
||
| `055d4a81` | 4,076 | histogram |
|
||
| `055d4a82` | 11,032 | waveform |
|
||
| `055d4a83` | 11,502 | waveform |
|
||
| `055d4a84` | 13,424 | waveform |
|
||
| `055d4a85` | 8,746 | waveform |
|
||
|
||
Consistent with real file sizes (corpus `.IDFH` ≈ 3.7–25 KB, `.IDFW` ≈
|
||
8.6–15.8 KB), and the histogram is unmistakably the small one. ⚠ Inferred,
|
||
not proven: the sizes sum to 48,780 while monitor status reports 57,344 bytes
|
||
used, so ~8.5 KB of overhead is unaccounted for.
|
||
|
||
### `SUB 0x0C` — waveform record, and it carries the job metadata
|
||
|
||
**Length `0xD2` = 210 bytes — identical to Series III.** Contents confirmed
|
||
across all 5 events:
|
||
|
||
- the event key, echoed
|
||
- date + time (`17 09 07 ea` → 23 Sep 2026, then `10 21` → 16:33 — matching
|
||
the actual bench recording time)
|
||
- title note `"Location"`
|
||
- **the project string** — `"Univ of Pitt-1st Yr Housing-Loc1 Ruskin"`
|
||
- serial `"UM12947"`
|
||
- channel labels `Tran` / `Vert` / `Long` / `Mic` — the same labels Series III
|
||
uses, and the same label-relative float32 layout
|
||
- per-event float32 peaks: 3.5152, 1.3720, 2.3542, 3.5152, 0.4227 in/s
|
||
across the five events (varied deliberately during recording)
|
||
|
||
**This closes the biggest open question for the call-home-receiver goal.**
|
||
The job identity strings (`project` / `client` / `operator` / `setup`) that
|
||
today arrive only via Thor's `.txt` sidecar — and which no amount of sample
|
||
decoding can reconstruct — are **available over the wire from `0x0C`**. A
|
||
direct-to-SFM event need not arrive with blank metadata.
|
||
|
||
### `SUB 0x0A` — partial record, histogram only
|
||
|
||
`0x0A` returned `len = 0x1E` (30 B) for the histogram and `len = 0x00` for all
|
||
four waveforms. The histogram payload carries **two timestamps** and the
|
||
ASCII string `"\r Vert: 0.300 in/s"` — structurally the Series III
|
||
**monitor-log partial record** (`0x2C` type), which likewise holds a start/stop
|
||
pair and a `"Geo: <float> in/s"` trigger string.
|
||
|
||
So on the Micromate the division of labour is: `0x0A` describes interval-style
|
||
records, `0x0C` describes triggered events. Series III uses `0x0A`'s
|
||
*response length* (`0x46` vs `0x2C`) to tell real events from boundaries;
|
||
that discriminator does not apply here.
|
||
|
||
### DLE stuffing in responses — confirmed present
|
||
|
||
Earlier marked untested. The `0x0C` timestamp field contains `10 10`, which
|
||
destuffs to a single `0x10` and yields a sensible clock reading. **Responses
|
||
are DLE-stuffed**, so a parser must destuff before applying field offsets.
|
||
|
||
---
|
||
|
||
## 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]`.
|
||
4b. **Event keys are a sequential counter**, not flash addresses.
|
||
4c. **`1E`/`1F` return the event size**, where Series III returns an offset.
|
||
4d. **`0x0A` vs `0x0C` split by record type**, not by the `0x46`/`0x2C`
|
||
length discriminator Series III uses.
|
||
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**, firmware **`11.0CB`** (Blastware line), on the bench via
|
||
USB, **zero events stored** (memory free == total). Read commands only.
|
||
Every response in this document was checksum-validated.
|
||
|
||
⚠ Firmware is the single biggest caveat on this document. Every finding here
|
||
is from one unit on the Blastware build. A `11.0BD` unit has not been
|
||
touched.
|
||
|
||
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.
|