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
This commit is contained in:
2026-09-30 14:08:27 -04:00
co-authored by Claude Opus 5
parent ea229ed19d
commit 080fb92ba2
3 changed files with 161 additions and 16 deletions
+23 -12
View File
@@ -231,29 +231,40 @@ def test_escaped_bytes_land_in_the_right_field():
assert f.checksum_valid
# The real 11.0BD POLL data section, UM20147 over USB 2026-09-30. This
# replaces a synthesised frame -- the Thor firmware line was inference-only
# until this capture.
_POLL_BD_DATA = bytes.fromhex(
"30000000000000000000000000005649"
"6e7374616e74656c000600c3f04a00e4"
"194f0052024d4d2f495345452f530058"
"f406001f60c0755760c075"
)
def test_thor_firmware_line_survives_destuffing():
"""⚠ flags = 0x03 is ETX, so it arrives as `10 03`.
A parser that does not destuff ends the frame at byte 2 on half the fleet.
SYNTHESISED frame, but the BEHAVIOUR IS CONFIRMED on real hardware:
UM20147 (11.0BD) was read over USB on 2026-09-30 and reported
firmware_line="thor", which is only reachable if `10 03` in the flags
position destuffed correctly. Raw BD bytes are still not in the repo, so
this frame stays synthesised -- built by re-stuffing the captured POLL
probe reply with flags flipped to 0x03, so the only difference from a real
frame is the one byte under test.
The data section is REAL (UM20147, 11.0BD); the frame wrapper is rebuilt,
which is sound because the framing is verified 251/251 elsewhere in this
file. scratch/mm_frame_parse.py reported payload=64 for this frame, and
the assertion below pins that, so the reconstruction is checked rather than
assumed.
"""
real = unstuff(RSP_POLL_PROBE[1:-1])[:-1]
payload = bytes([real[0], FLAGS_THOR]) + real[2:]
synth = bytes([STX]) + stuff(payload + bytes([checksum(payload)])) + bytes([ETX])
payload = bytes([0x00, FLAGS_THOR, 0xA4, 0x00, 0x00]) + _POLL_BD_DATA
assert len(payload) == 64, "the parser reported payload=64 for this frame"
wire = bytes([STX]) + stuff(payload + bytes([checksum(payload)])) + bytes([ETX])
assert bytes([DLE, ETX]) in synth, "flags must be escaped on the wire"
(f,) = MicromateFrameParser().feed(synth)
assert bytes([DLE, ETX]) in wire, "flags 0x03 must be escaped on the wire"
(f,) = MicromateFrameParser().feed(wire)
assert f.flags == FLAGS_THOR
assert f.firmware_line == "thor"
assert f.sub == 0xA4
assert f.request_sub == 0x5B
assert f.checksum_valid
assert b"MM/ISEE/S\x00" in f.data, "the shorter BD model string"
def test_an_escaped_checksum_byte_is_read_correctly():