Commit Graph
3 Commits
Author SHA1 Message Date
serversdownandClaude Opus 5 080fb92ba2 test(micromate): real 11.0BD fixtures replace the synthesised ones
UM20147 captured over USB with mm_client_check.py --capture, read out with
scratch/mm_frame_parse.py: 11 frames each direction, 0 bad checksums.  The two
BD-specific response data sections are now verbatim fixtures; only the frame
wrapper is rebuilt, which is sound because the framing is verified 251/251
elsewhere in the same file, and the reconstruction is pinned against the
payload length the parser reported.

Every field lands where the forward-offset model says it should:

  data[0]        = 0x30 = 48 = 59 - 11   (CB declares 0x2C = 44; the +4 holds)
  content[1]     = 0x0e   monitoring
  content[2:10]  = 30 Sep 2026 14:00:49  matches the wall clock
  content[34:36] = 3.50 V
  content[36:44] = 15,000,000 total / 14,848,448 free

AND THE WARNING IS NOW DEMONSTRATED ON THE UNIT IT PREDICTED.  Series III reads
battery from data[-10:-8], which on this block is e1 c0 -> 577.92 V.  That is
the exact figure the A/B section named, and there is now a test asserting it,
so the reason the offsets are forward cannot be quietly refactored away.

Two corrections to the reference:

1. The four extra BD bytes are `0f a0 00 04`, not `0f a0 00 00`.  Only the
   first two look fixed.  Nothing reads them, but the last is not padding.

2. POLL content[3] VARIES BETWEEN UNITS -- 0x50 on UM12947, 0x56 on UM20147.
   It is printable in both cases ("P" and "V"), which is a second, independent
   reason the POLL block cannot be parsed by scanning for printable runs: a
   scan returns "PInstantel" on one unit and "VInstantel" on the other.  The
   fixed offset content[4] was the right call for a reason I had not seen.

The model string sits at content[26] on both firmware lines; only its tail
differs, so anchoring on "MM/" remains correct.

98 micromate tests pass.  Full suite unchanged at 16 pre-existing failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-30 14:08:27 -04:00
serversdownandClaude Opus 5 b55e4e946a verify(micromate): 11.0BD confirmed on hardware -- every inference held
UM20147 read over USB by mm_client_check.py.  The Thor firmware line was
entirely inference until now, and two of its three differences were covered
only by SYNTHESISED test frames.  All three held:

- flags = 0x03 identifies the line -> reported firmware_line="thor".  That is
  only reachable if `10 03` in the flags position destuffs before indexing,
  since 0x03 is ETX -- so the escaped-flags case is confirmed too.
- the model string is shorter -> reported "MM/ISEE/S" exactly.  Anchoring the
  search on b"MM/" rather than a fixed span is what made this work.
- the 0x1C block is 4 bytes longer with the extras TRAILING, so from-the-start
  offsets survive -> battery 3.55 V and a clock correct to the second.

That last one is the one that mattered.  Series III's from-the-end offsets
would have given this unit 577.92 V, which is why mm_client_check watches for
an impossible voltage: it is a free self-check on exactly the inference most
likely to be wrong.

Also clean: serial UM20147, 5 setups (factory.MMB -> test2.mmb), active setup
test2.mmb, 15,000,000 B total and free, 21 reads / 2,165 B in.

"One protocol stack drives the whole fleet regardless of firmware line" -- the
headline finding of 2026-09-23 -- is now demonstrated by a working client
rather than by matching response SUBs.

Test and docstring claims downgraded from inference to confirmed where the
hardware settled them, and left as synthesised-frame notes where it did not:
the BEHAVIOUR is confirmed but raw BD bytes are still not in the repo.

Added --capture DIR to mm_client_check: writes a raw_bw_*/raw_s3_* pair in the
layout scratch/mm_frame_parse.py already reads, so a run on an unfamiliar unit
becomes a test fixture without setting up a relay.  Verified by round-tripping
its own output through that parser: 28 frames, 0 bad checksums.

Still not covered: a download from a BD unit (UM20147 had no events stored, so
the chunk walk remains CB-only), raw BD fixture bytes, a monitoring unit, a
nearly-full buffer, and the inbound call-home session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
2026-09-30 13:17:53 -04:00
serversdownandClaude Opus 5 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
2026-09-27 05:24:24 -04:00