6 Commits
Author SHA1 Message Date
serversdownandClaude Opus 5 45007e12d8 docs(series4): the firmware images are unencrypted and self-documenting
Both MICROMATE(CB).BIN and MICROMATE(BD).BIN are plain code and data --
entropy 6.08 bits/byte, big-endian vector table at 0x4010_30xx, ~16,700
extractable strings including the developers' own debug printf formats with
function names intact.

This answers, from strings alone, questions I had scoped as needing a live
modem capture.

The call-home state machine, verbatim:
  ACH_NOT_STARTED -> ACH_IDLE -> ACH_INITIALIZING -> ACH_CONNECTING
  -> ACH_CONNECTED -> ACH_TRANSFER_DATA -> ACH_RETRY / ACH_QUITTING

And with it:

- Retry limit is three ("three attempts and it's over").
- ExpectedCommunicationsDetected() gates the session: if the host does not say
  something the unit recognises, the call is cancelled and rescheduled after
  TimeBetweenRetries. A homebrew receiver must satisfy this check or units
  retry forever -- exactly the BE12599 failure mode.
- The unit stops monitoring to call home and restarts after
  (Send CMD_STOP_MONITOR / CMD_START_MONITOR), so monitoring state around a
  call is the device's own doing.
- Calls are not re-entrant.
- CMD_CALLHOME_CONNECTION_CONFIRMED exists as a state distinct from
  CONNECTION_COMPLETE, implying a handshake the host must complete before data
  flows.

Event delivery, inferred not confirmed: "All Events Uploaded" plus
"Mark/Unmark File" / "Delete Marked Events" / CMD_PURGE_EVENT_FLASH suggest
events are marked as transferred rather than deleted on send, with purging a
separate explicit act. If so, a receiver that fails to mark would see the same
events re-offered every call. Needs a live capture or disassembly to confirm.

The firmware also embeds its own HTML user manual, documenting modem mode
(Generic vs USB to PC), the modem baud options (9600-230400, confirming 115200
is a setting not a fixed rate), modem relay/warmup, record modes, and a
scheduler downloaded from THOR that pairs with CMD_CALLHOME_SET_SCHEDULE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-23 18:28:40 -04:00
serversdownandClaude Opus 5 38ad58d4a4 docs(series4): A/B the two firmware lines -- the protocol is the same
UM12947 (11.0CB, Blastware line) and UM20147 (11.0BD, Thor line) each given
the identical read-only sweep on the bench. Both answer Series III command
frames: all ten read SUBs, correct response-SUB rule, valid DLE-aware
checksums, working two-step probe/data reads.

The firmware line does not change the wire protocol. One protocol stack can
drive the whole fleet regardless of build, which downgrades "standardise the
fleet on one firmware" from a prerequisite to an optional convenience.

Two differences do exist:

1. Response payload[1] (flags) is 0xC5 on the Blastware line and 0x03 on the
   Thor line, constant across all ten SUBs on both units -- so the build is
   detectable from any response without reading device info. Two units, one
   each, so this is a strong hypothesis rather than a proven encoding.

   Note 0x03 is ETX, so it arrives DLE-escaped as 10 03 on Thor-line units. A
   parser that does not destuff will mis-locate every field by one byte on
   half the fleet.

2. SUB 0x1C (monitor status) is 4 bytes longer on the Thor line, 0x30 vs
   0x2C, with four extra trailing bytes (0f a0 00 00, purpose unknown).

That second one breaks relative-to-end parsing: Series III reads battery and
memory from the end of the 0x1C block, and those offsets yield a battery
reading of 577.92 V on UM20147. Parse forward from the declared length, not
backward from the end. With the shift applied, UM20147 reads 3.81 V and
15,000,000 bytes total/free.

Also noted: ID string is MM/ISEE/S/IO on the Blastware unit and MM/ISEE/S on
the Thor one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-23 18:20:11 -04:00
serversdownandClaude Opus 5 492b6683a4 docs(series4): fleet firmware audit, and retract the Thor-compatibility claim
Physical audit of all nine Micromates: 4 on the Blastware line (11.0CB), 2 on
the Thor line (11.0BD), 3 pre-split (11.0AK x2, 10.90GC). The Blastware line
is already the plurality, which makes "standardise on Blastware" less
disruptive than it first looked.

Cross-checked against a store-derived audit (firmware is recorded in every
.sfm.json as extensions.idf_report.version): 7 of 9 agree. The two that differ,
UM6047 and UM14133, are the most recently deployed and were reflashed after
their last stored event -- so the store reconstructs firmware history without
touching a unit, but lags reality by one deployment.

RETRACTION: an earlier draft suggested UM12947's trouble with Thor was
explained by its Blastware firmware. Not supported. Ped Bridge runs UM11402
(11.0BD) and UM11719 (11.0CB) side by side from the same deploy date and both
call Thor fine -- UM11719 has 331 Thor-collected events while on 11.0CB. A
Blastware-line unit does feed Thor, so the CB/BD split is not "which host can
collect from it", and UM12947's problem remains unexplained.

What is actually established is narrower: a 11.0CB unit answers Series III
command frames. Whether a 11.0BD unit does is untested -- and UM20147 (11.0BD)
is on the bench, so that is one A/B away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-23 17:08:47 -04:00
serversdownandClaude Opus 5 73eaa0a6ac docs(series4): the event chain, walked end to end
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
2026-09-23 16:39:11 -04:00
serversdownandClaude Opus 5 b9c52442a7 docs(series4): the Series III behaviour is firmware-conditional
The bench unit reports 11.0CB -- Instantel's *Blastware* firmware line. It
almost certainly answers Series III commands because it is in Blastware mode,
not because the Micromate natively speaks Series III. Instantel ships two
lines: 11.0CB (Blastware) and 11.0BD (THOR, Vision, Vision II).

That also explains the two-ACH-server problem as designed behaviour rather
than misconfiguration.

Corpus firmware audit: 932 event files from 11.0AK, 83 from 10.90GC. UM12947
itself produced 10.90GC files in production last year and reports 11.0CB now,
so units get reflashed and firmware is not stable per-unit over time.

Records the resulting strategic fork (standardise on the Blastware line vs
reverse-engineer the Thor line) with the four unknowns that decide it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-23 16:04:42 -04:00
serversdownandClaude Opus 5 095834183e 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
2026-09-23 15:59:17 -04:00
+564
View File
@@ -0,0 +1,564 @@
# 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.
### Fleet audit (physical, 2026-09-22/23)
| unit | firmware | line | location |
|---|---|---|---|
| UM11719 | `11.0CB` | Blastware | Ped Bridge Loc 2 |
| UM6047 | `11.0CB` | Blastware | Brookville Loc 9 |
| UM12947 | `11.0CB` | Blastware | **bench** |
| UM14133 | `11.0CB` | Blastware | Pitt-Music Bldg Loc 1 |
| UM11402 | `11.0BD` | Thor | Ped Bridge Loc 1 |
| UM20147 | `11.0BD` | Thor | **bench** |
| UM13981 | `11.0AK` | pre-split | RKM Loc 1 |
| UM20146 | `11.0AK` | pre-split | Karns Loc 2 |
| UM12420 | `10.90GC` | pre-split | RKM Loc 2 |
**4 Blastware / 2 Thor / 3 pre-split.** The Blastware line is already the
plurality, which makes "standardise on Blastware" less disruptive than it
first appeared.
A store-derived audit (firmware is recorded in every `.sfm.json` sidecar as
`extensions.idf_report.version`) agreed with the physical audit on **7 of 9**.
The two that differed — UM6047 and UM14133 — are the most recently deployed
units, reflashed after their last stored event. Useful technique: the fleet's
firmware history is reconstructable from the store without touching a unit,
but it lags reality by one deployment.
Firmware is **not stable per-unit over time** — five of nine have been
reflashed at least once. Any fleet-wide claim needs a fresh audit.
### ⚠ Retraction: firmware line does NOT determine Thor compatibility
An earlier draft of this document suggested that UM12947's trouble with Thor
was explained by its being on the Blastware build. **That is not supported.**
Ped Bridge runs UM11402 (`11.0BD`) and UM11719 (`11.0CB`) side by side, both
deployed 2026-04-20, and **both call Thor successfully** — UM11719 has 331
Thor-collected events in the store while on `11.0CB`, through 2026-08-23.
So a Blastware-line unit does feed Thor. Whatever the CB/BD split changes, it
is not "which host software can collect from it", and UM12947's specific
problem remains unexplained.
**What is actually established:** a `11.0CB` unit answers Series III command
frames. Whether a `11.0BD` unit does is **untested** — and UM20147 (`11.0BD`)
is on the bench, which makes that a direct A/B away.
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.
---
## A/B: Blastware build vs Thor build (2026-09-23)
UM12947 (`11.0CB`) and UM20147 (`11.0BD`) were each put on the bench and given
the identical read-only sweep. **Both answer Series III command frames.**
| | UM12947 `11.0CB` | UM20147 `11.0BD` |
|---|---|---|
| POLL answers | ✓ | ✓ |
| All 10 read SUBs answer | ✓ | ✓ |
| `response_SUB = 0xFF − req` | ✓ | ✓ |
| DLE-aware checksum valid | ✓ | ✓ |
| Two-step probe/data read | ✓ | ✓ |
| ID string | `MM/ISEE/S/IO` | `MM/ISEE/S` |
| **flags byte** | **`0xC5`** | **`0x03`** |
| **`0x1C` length** | **`0x2C`** | **`0x30`** |
**The firmware line does not change the wire protocol.** One protocol stack
can drive the whole fleet regardless of which build a unit is on. This is the
single most consequential finding so far: "standardise the fleet on one
firmware" becomes an *optional* convenience rather than a prerequisite for
building a call-home receiver.
### The two differences that do exist
**1. The flags byte identifies the build.** Response `payload[1]` is `0xC5`
on the Blastware line and `0x03` on the Thor line, constant across all ten
SUBs on both units. That makes firmware line detectable from *any* response,
without reading device info. ⚠ Two units, one each — treat as a strong
hypothesis, not a proven encoding.
Note `0x03` is ETX, so on Thor-line units it arrives DLE-escaped as `10 03`.
A parser that fails to destuff will mis-locate every field by one byte on
exactly half your fleet.
**2. `SUB 0x1C` (monitor status) is 4 bytes longer on the Thor line** —
`0x30` vs `0x2C` — with four extra trailing bytes (`0f a0 00 00`, purpose
unknown).
⚠ **This breaks relative-to-end parsing.** Series III reads battery and
memory from the *end* of the `0x1C` block (`[-10:-8]`, `[-8:-4]`, `[-4:]`).
Those offsets are correct on `11.0CB` and wrong on `11.0BD` — applying them
blindly to UM20147 yields a battery reading of **577.92 V**. Parse forward
from the declared length instead of backward from the end.
With the offsets shifted by 4, UM20147 reads correctly: battery **3.81 V**,
memory 15,000,000 total and free (no events stored).
## 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.
4e. **Response `payload[1]` (flags) encodes the firmware line** — `0xC5`
Blastware, `0x03` Thor — where Series III has a constant `0x10`.
5. **Modem serial rate is 115200**, not 38400 (per TMI provisioning practice;
not independently verified here).
---
## The firmware images are unencrypted — and they document themselves
`ref-stuff/micromate-firmware/MICROMATE(CB).BIN` and `MICROMATE(BD).BIN`,
~2.77 MB each and within 192 bytes of one another.
- **Entropy 6.08 bits/byte** — neither encrypted nor compressed. Plain code
and data.
- Header is a **big-endian vector table**, handlers at `0x4010_30xx`.
- **~16,700 extractable strings**, including the developers' own debug
`printf` format strings with function names intact.
This is a legitimate interoperability reference for hardware TMI owns, and it
short-circuits work I had scoped as "only answerable from a live modem
capture".
### The call-home state machine, verbatim
```
ACH_NOT_STARTED → ACH_IDLE → ACH_INITIALIZING → ACH_CONNECTING
→ ACH_CONNECTED → ACH_TRANSFER_DATA
→ ACH_RETRY / ACH_QUITTING (also ACH_STARTED)
```
Supporting strings:
```
ACH: Entry StartCallHome()
ACH: CallHome_task ; CheckAliveTime CANCEL ; TimeBetweenRetries = %d
ACH: CallHome_task ; !ExpectedCommunicationsDetected() CANCEL ; TimeBetweenRetries = %d
ACH: CallHomeCommectionCompleteProcessing() ACH=%s EAMWC=%s
ACH: %s() three attempts and it's over
ACH: %s() Send CMD_START_MONITOR
ACH: %s() Send CMD_STOP_MONITOR
ACH: Start Ignore request, Call Home is in progress
```
What this tells us without a single captured packet:
1. **Retry limit is three** — "three attempts and it's over".
2. **`ExpectedCommunicationsDetected()` gates the session.** If the host does
not say something the unit recognises, the call is *cancelled* and
rescheduled after `TimeBetweenRetries`. A homebrew receiver must satisfy
this check or units will retry forever — which is exactly the failure mode
seen on BE12599.
3. **The unit stops monitoring to call home and restarts afterwards**
(`Send CMD_STOP_MONITOR` / `CMD_START_MONITOR`). Relevant to any
wedged-unit rescue: the monitoring state around a call is the device's own
doing, not ours.
4. **Calls are not re-entrant** — "Call Home is in progress" is ignored.
### Internal command table
`CMD_CALLHOME`, `CMD_CALLHOME_CANCEL`, **`CMD_CALLHOME_CONNECTION_CONFIRMED`**,
`CMD_CALLHOME_CONNECTION_COMPLETE`, `CMD_CALLHOME_SET_SCHEDULE`,
`CMD_CALLHOME_CLEAR_SCHEDULE`, `CMD_STOP_CALLHOME_FILETRANSFER`,
`CMD_DUTYCYCLE_AUTOCALLHOME`, `CMD_PURGE_EVENT_FLASH`.
`CONNECTION_CONFIRMED` as a distinct state from `CONNECTION_COMPLETE` implies
a **handshake the host must complete before data flows** — the concrete shape
of `ExpectedCommunicationsDetected()`.
### Event delivery — the mechanism, probably
```
All Events Uploaded
Mark/Unmark File Delete Marked Events Marked Events were Deleted
MONITOR::MESG PURGE_EVENT_FLASH BEGIN / END
```
A **marking** mechanism exists, alongside a distinct "all uploaded" terminal
state. 🔶 **Inferred:** events are *marked* as transferred rather than
deleted on send, and purging is a separate explicit act. If so, a receiver
that fails to mark would see the same events re-offered every call — the
question that gates a safe homebrew receiver. **Not yet confirmed**; needs
either a live call-home capture or disassembly around these strings.
### A full user manual is embedded
The firmware carries its own HTML help, which documents configuration we would
otherwise have to infer:
- **Modem mode**: `Generic` (through a modem) vs `USB to PC`.
- **Modem baud**: 9600 / 19200 / 38400 / 57600 / 115200 / 230400 — "must match
the expected rate of the PC or modem". Confirms 115200 is a *setting*, not
a fixed rate.
- **Modem relay + warmup** (0–300 s), auxiliary mode, warning/alarm hold.
- **Record modes**: Waveform, Waveform Manual, Histogram, Histogram-Combo;
sample rates 1024 / 2048 / 4096.
- **A scheduler downloaded from THOR** that can start/stop monitoring, change
record mode, trigger a call home, or run a self check on a daily/weekly
schedule. Pairs with `CMD_CALLHOME_SET_SCHEDULE`.
### Still worth doing
Diffing the two images should isolate exactly what the CB/BD split changes —
we know the wire protocol is not it, and the flags byte (`0xC5` vs `0x03`)
gives a concrete anchor to search for.
## ⚠ 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.