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:
@@ -63,6 +63,27 @@ MS_LATE = bytes.fromhex( # 55 B, from_20260925_011403_
|
||||
"e4e1c000e3e1c0"
|
||||
)
|
||||
|
||||
# ── Real 11.0BD bytes (UM20147, captured over USB 2026-09-30) ─────────────────
|
||||
#
|
||||
# The Thor firmware line, which was pure inference until this capture. These
|
||||
# are the two responses where BD differs from CB. Captured with
|
||||
# `mm_client_check.py --capture` and read out of the resulting pair with
|
||||
# scratch/mm_frame_parse.py, so the data sections are verbatim; only the frame
|
||||
# wrapper is rebuilt, and the framing is independently verified 251/251.
|
||||
|
||||
POLL_BD = bytes.fromhex( # 59 B -- flags 0x03, and the SHORTER model string
|
||||
"30000000000000000000000000005649"
|
||||
"6e7374616e74656c000600c3f04a00e4"
|
||||
"194f0052024d4d2f495345452f530058"
|
||||
"f406001f60c0755760c075"
|
||||
)
|
||||
MS_BD_MONITORING = bytes.fromhex( # 59 B -- FOUR BYTES LONGER than CB's 55
|
||||
"3000000000000000000000000e1e0907"
|
||||
"ea600e00310000000000000000000000"
|
||||
"00000000000000000000000000015e00"
|
||||
"e4e1c000e291c00fa00004"
|
||||
)
|
||||
|
||||
_SETUP_PAD = 266 - CONTENT
|
||||
|
||||
|
||||
@@ -398,3 +419,75 @@ def test_context_manager_opens_and_closes():
|
||||
assert t.is_connected()
|
||||
assert mm.connect().serial == "UM12947"
|
||||
assert not t.is_connected()
|
||||
|
||||
|
||||
# ── The Thor firmware line, on real bytes ─────────────────────────────────────
|
||||
|
||||
def test_bd_constants_have_the_lengths_the_parser_reported():
|
||||
assert len(POLL_BD) == 59
|
||||
assert len(MS_BD_MONITORING) == 59, "CB is 55; BD is four bytes longer"
|
||||
assert MS_BD_MONITORING[0] == 0x30, "low-byte length 48 = 59 - 11"
|
||||
assert MS_IDLE[0] == 0x2C, "the CB equivalent declares 44"
|
||||
|
||||
|
||||
def test_connect_on_a_thor_line_unit():
|
||||
"""UM20147, real POLL bytes. flags 0x03 is ETX, so it arrives as `10 03`."""
|
||||
mm, _ = client([
|
||||
frame(0xA4, POLL_BD, flags=FLAGS_THOR), frame(0xEA, SERIAL),
|
||||
frame(0xB6, STATE_MONITORING), frame(0xBE, setup_response("test2.MMB")),
|
||||
])
|
||||
info = mm.connect()
|
||||
assert info.firmware_line == "thor"
|
||||
assert info.model == "MM/ISEE/S", "the BD model string is shorter than CB's"
|
||||
assert info.manufacturer == "Instantel"
|
||||
assert info.monitoring is True
|
||||
|
||||
|
||||
def test_poll_content_3_is_not_a_constant():
|
||||
"""⚠ 0x50 on UM12947, 0x56 on UM20147 -- it VARIES between units.
|
||||
|
||||
This is why the manufacturer is read at the fixed offset content[4] and not
|
||||
by scanning: content[3] is printable in both cases ("P" and "V"), so a
|
||||
printable-run scan would return "PInstantel" on one unit and "VInstantel" on
|
||||
the other. Whatever the byte is, it is not a stable marker to anchor on.
|
||||
"""
|
||||
assert _content(POLL)[3] == 0x50
|
||||
assert _content(POLL_BD)[3] == 0x56
|
||||
assert chr(_content(POLL_BD)[3]) == "V"
|
||||
|
||||
|
||||
def test_get_state_on_a_thor_line_unit():
|
||||
"""⚠ THE test for the forward-offset decision. Real UM20147 bytes.
|
||||
|
||||
The 0x1C block is four bytes longer here, and the extras are TRAILING, so
|
||||
offsets measured from the start of content are unmoved. Series III reads
|
||||
battery and memory from the END of this block; test_series_iii_offsets_...
|
||||
below shows what that produces.
|
||||
"""
|
||||
mm, _ = client([frame(0xE3, MS_BD_MONITORING, flags=FLAGS_THOR)])
|
||||
st = mm.get_state()
|
||||
|
||||
assert st.monitoring is True
|
||||
assert st.device_time == datetime.datetime(2026, 9, 30, 14, 0, 49)
|
||||
assert st.battery_volts == 3.50
|
||||
assert st.memory_total_bytes == 15_000_000
|
||||
assert st.memory_free_bytes == 14_848_448
|
||||
|
||||
|
||||
def test_series_iii_from_the_end_offsets_give_577_volts_on_a_bd_unit():
|
||||
"""The exact number the protocol reference warned about, now demonstrated.
|
||||
|
||||
577.92 V is not a plausible battery reading for anything, which is what
|
||||
makes it a free self-check -- bridges/mm_client_check.py watches for it.
|
||||
"""
|
||||
assert int.from_bytes(MS_BD_MONITORING[-10:-8], "big") / 100 == 577.92
|
||||
|
||||
|
||||
def test_the_four_extra_bd_bytes_are_not_all_zero():
|
||||
"""⚠ The reference records them as `0f a0 00 00`; UM20147 sent `0f a0 00 04`.
|
||||
|
||||
Only the first two bytes look fixed. Nothing reads them, but a future
|
||||
decoder must not treat the last one as padding.
|
||||
"""
|
||||
assert _content(MS_BD_MONITORING)[44:] == bytes.fromhex("0fa00004")
|
||||
assert _content(MS_IDLE)[44:] == b"", "the CB block has no such tail"
|
||||
|
||||
Reference in New Issue
Block a user