5fe99568a20cf5f299feb0749a3687901cdfee8a
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5fe99568a2 |
feat(micromate): framing layer -- and three spec rules the bytes refuted
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 |
||
|
|
8a1d1c3f17 |
docs(series4): spec the live client, so tomorrow is implementation not design
Turns docs/micromate_protocol_reference.md into a build plan for the missing half of micromate/ -- it is codec-only today with no way to talk to a unit. Layout mirrors minimateplus/: framing, protocol, client. Transport is REUSED -- minimateplus/transport.py is byte-level and protocol-agnostic, and already handles the RV50/RV55 habit of emitting RING/CONNECT to a caller. But minimateplus/framing is deliberately NOT shared: the two framings differ in ways that look small and are not, and a shared module would accumulate `if series ==` branches until neither case is readable. Records the three things that make S3FrameParser useless on Series IV (bare STX, 0xC5/0x03 flags, uniform 10 XX -> XX destuffing with no inner-frame carve-out), and the traps that have already cost time: the declared length is a uint16 BE and reading it as a byte under-reads SUB 0x1A by 47x; SUB 0x1C is four bytes longer on the Thor line so parse forward not backward, or a BD unit reports 577.92 V; the monitoring flag must be tested non-zero; and the SUB byte itself can arrive DLE-escaped, so destuff before indexing. Scope is READ-ONLY, stated with the reasoning: no command has ever been originated against a unit by this project, and keeping that true through the read client means the first thing we ever send to a customer's instrument is a deliberate decision rather than a side effect of a client that grew a method. Tests are specified to embed frames as hex constants rather than read fixtures, since bridges/captures/ and tests/fixtures/ are both gitignored -- with a table of which cases matter and why, and a round-trip assertion against scratch/mm_frame_parse.py, which is already known good across three sessions at zero bad checksums. Steps 1-2 need no hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL |