Step 1 of docs/micromate_client_spec.md: micromate/framing.py plus 31
offline tests. Every rule was checked against the captures BEFORE being
written, which is the only reason this commit is not a bug.
Three things the spec asserted are wrong, all of which fail silently:
1. Requests are NOT plain Series III frames. A Micromate escapes four
byte values -- 0x02 0x03 0x04 0x10 -- where Series III escapes one.
minimateplus.build_bw_frame reproduces 161 of Thor's 218 captured read
frames; build_request() reproduces 218/218. The 57 it missed include
EVERY 0x5A download (offset 0x0400 puts a literal 0x04 in offset_hi)
and the scheduler enable. An unescaped 0x03/0x04 terminates the frame,
so the unit never answers -- indistinguishable from a dead unit, and
event download would have hit it on the first request ever sent.
2. The checksum is plain SUM8 of the destuffed payload, not the DLE-aware
variant. 251/251 both directions. The DLE-aware form is correct
paired with Series III destuffing, which leaves an escaped byte as two
bytes; after uniform destuffing it subtracts the correction twice and
disagrees with the wire on 55 of 251 responses.
scratch/mm_frame_parse.py shipped with exactly that pairing and looked
clean only because it accepts either rule -- so it labelled those 55
"SUM8" and never flagged one bad. "Zero bad checksums" was true and
carried no information. A tool that tries N candidate rules cannot
falsify any of them. Fixed to validate against SUM8 alone.
3. SUB 0x5A is a 1024-byte chunk loop, not one request per event. Thor's
form, verified on all six bench events (4,076 -> 13,424 B): chunks =
ceil(size/1024), offset = min(1024, size - 1024*i) as a byte count,
params[2:4] = the byte offset, response data = offset + 11.
sum(offsets) == size exactly, every time.
This does not retract the earlier single-request observation -- that
used offset_hi = 0x10, which in Series III is the bulk-stream marker,
so it is plausibly a distinct streaming mode returning several frames.
Those captures never landed in the repo, so it cannot be re-derived.
Implement Thor's form; the other is worth one bench test.
Also: a 0x10 inside request params needs no special handling (settled --
Thor sends it, the wire doubles it), so the planned NotImplementedError
guard is gone. declared_length -> probe_length, because it is only
meaningful in a probe reply and Thor never probes.
Synthesised test frames are marked and each says what it stands in for.
The flags=0x03 case is the only coverage of the Thor firmware line -- it
wants a real 11.0BD capture next time UM20147 is on a bench.
No writes. Read-path framing only; nothing here can originate a command.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL