From 0cfec2f09872e9ad923abbd35f043541c8d037b5 Mon Sep 17 00:00:00 2001 From: serversdown Date: Thu, 1 Oct 2026 16:04:21 -0400 Subject: [PATCH] tooling(micromate): --event selects what to download, incl. 'largest' The tool downloaded only the first event, which on UM20147 is a 4,796 B histogram -- so it could not reach the 72,560 B event that is the whole point of the uint32 chunk-offset change. --event takes a key in hex, 'first', 'largest' or 'all'. 'largest' is the one worth running on an unfamiliar unit, since it is what exercises offsets past 64 KB; the output flags any event over 64 KB as carrying into params[1], and prints the chunk count so a 71-chunk walk is visible as such. Selected events are downloaded from inside iter_events(), so the chain cursor sits where THOR's would -- non-selected events are walked past without a download, which the browse walk already attests is fine. Also: connect() returning nothing now prints a hint rather than just a bare timeout. /dev/ttyACM* numbering shifts whenever another CDC-ACM device is plugged in, and this cost a debugging session when the tool was aimed at a TMI-Seismo MCU instead of the Micromate. `ls -l /dev/serial/by-id/` names each device and is stable, so the hint says so. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL --- bridges/mm_client_check.py | 73 ++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/bridges/mm_client_check.py b/bridges/mm_client_check.py index 87fe201..6cc71d2 100644 --- a/bridges/mm_client_check.py +++ b/bridges/mm_client_check.py @@ -232,6 +232,18 @@ def _decode(blob: bytes, key: bytes, record: bytes) -> None: print(f" saved to {out} for offline analysis") +def _select(refs, which: str): + """Pick which events `--download` fetches.""" + which = which.strip().lower() + if which == "first": + return refs[:1] + if which == "all": + return refs + if which == "largest": + return [max(refs, key=lambda r: r.size)] + return [r for r in refs if r.key_hex.lower() == which] + + def step(label: str, fn): """Run one read, report how long it took and what it returned.""" t0 = time.monotonic() @@ -259,7 +271,12 @@ def main() -> int: "and negotiates its own rate.") ap.add_argument("--timeout", type=float, default=10.0) ap.add_argument("--download", action="store_true", - help="also download the first stored event (read-only)") + help="also walk the event chain and download (read-only)") + ap.add_argument("--event", default="first", metavar="WHICH", + help="which event --download fetches: a key in hex " + "(e.g. 055d4a83), 'first', 'largest', or 'all'. " + "'largest' is the one worth running on an unfamiliar " + "unit -- it is what exercises offsets past 64 KB.") ap.add_argument("--capture", metavar="DIR", help="also write a raw_bw_*/raw_s3_*.bin pair to DIR, so " "this run can become a test fixture. Worth doing on " @@ -291,6 +308,12 @@ def main() -> int: try: info = step("connect() identity", mm.connect) + if info is None: + print("\n Nothing answered. If the port opened but no frame came back,") + print(" check it is actually a Micromate and not another CDC-ACM device —") + print(" /dev/ttyACM* numbering shifts when anything else is plugged in.") + print(" `ls -l /dev/serial/by-id/` names each device and is stable.") + return 3 if info: print(f" serial={info.serial} model={info.model} " f"fw={info.firmware_line} monitoring={info.monitoring}") @@ -345,24 +368,36 @@ def main() -> int: print(f" would be filed as {ref.filename}") if refs: - # Download via iter_events, which reproduces THOR's interleaved - # order -- the one with captures behind it. - print("\n download (first event, THOR's interleaved order):") - for ref in mm.iter_events(): - t1 = time.monotonic() - try: - result = mm.get_event(ref) # verify=True - except Exception as e: - print(f" {ref.key_hex}: {type(e).__name__}: {e}") - break - dt = max(time.monotonic() - t1, 1e-6) - n = sum(len(v) for v in getattr(result, "samples", {}).values()) - err = mm.decode_error(ref, result) - check = ("PVS agrees to %+.4f%%" % (100 * err) - if err is not None else "PVS check n/a (histogram)") - print(f" {ref.key_hex} {ref.record_type:9} {ref.size:6} B " - f"in {dt:.1f} s -> {n} samples, {check}") - break + wanted = _select(refs, a.event) + if not wanted: + print(f"\n --event {a.event!r} matched nothing") + else: + print(f"\n download ({len(wanted)} of {len(refs)}, " + f"THOR's interleaved order):") + want_keys = {r.key_hex for r in wanted} + # iter_events() walks the chain; download only the selected + # events, at the cursor position THOR would be at. + for ref in mm.iter_events(): + if ref.key_hex not in want_keys: + continue + n_chunks = -(-ref.size // 1024) + note = (" <- past 64 KB, carries into params[1]" + if ref.size > 0x10000 else "") + t1 = time.monotonic() + try: + result = mm.get_event(ref) # verify=True + except Exception as e: + print(f" {ref.key_hex} {ref.size:7} B " + f"FAILED {type(e).__name__}: {e}") + continue + dt = max(time.monotonic() - t1, 1e-6) + n = sum(len(v) for v in getattr(result, "samples", {}).values()) + err = mm.decode_error(ref, result) + check = ("PVS %+.4f%%" % (100 * err) if err is not None + else "PVS n/a (histogram)") + print(f" {ref.key_hex} {ref.record_type:9} " + f"{ref.size:7} B {n_chunks:3} chunks {dt:5.1f} s " + f"{n:6} samples {check}{note}") except ProtocolError as e: print(f"\n ABORTED {type(e).__name__}: {e}")