From 84ca10eb3cfb60dd9e92c864d722da1f76050e6b Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 25 Sep 2026 17:48:36 -0400 Subject: [PATCH] docs(series4): the Micromate USB host is FTDI + CDC-ACM only -- not Prolific A Micromate on an RX55 was unreachable from THOR. Isolated layer by layer with bridges/mm_probe.py, and the answer turned out to be the cable. The USB-A port is a HOST port with a fixed driver set, identical in both firmware lines: CDCACM FTDI MFS (mass storage) PRINTER HUB HC USBH 11 FTDISER strings and 22 CDCACM strings in each image, and ZERO matches for prolific / pl2303 / cp210 / ch34 / silabs in either. So a Prolific PL2303 cable (VID 067b) cannot work with a Micromate on any firmware -- no driver, no enumeration, no serial path. It needs an FTDI cable (VID 0403) or CDC-ACM. The isolation method is the part worth keeping. Eliminated in turn: public IP (static APN), firewall (probe got TCP connect in 268 ms from a whitelisted source), network path, baud, the unit's modem-type setting, and THOR itself (the probe bypasses it). Then the decisive pair -- a Linux box was swapped in for the unit on the SAME cable and modem: * POLL arrived at the serial side byte-perfect * a canned reply came back over TCP, full round trip in 700 ms So the modem, PAD config, firewall and network are all clean, and the only element that changed is what sits at the end of the cable. Substituting a known-good device converts "the unit is not answering" into "the unit is not receiving" -- very different problems. Adds scratch/fake_unit.py for that. Flagged: this explains the BENCH setup conclusively. It does NOT establish the cause of the 2026-09-22 field outage, which reportedly worked at first and degraded -- a wrong cable would not do that. Kept separate until that unit's cable is identified. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL --- docs/micromate_protocol_reference.md | 54 ++++++++++++++++++++++++++++ scratch/fake_unit.py | 33 +++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 scratch/fake_unit.py diff --git a/docs/micromate_protocol_reference.md b/docs/micromate_protocol_reference.md index ff07130..c124e4a 100644 --- a/docs/micromate_protocol_reference.md +++ b/docs/micromate_protocol_reference.md @@ -2067,6 +2067,60 @@ leak is reproducible and attributable to a specific UI action. 3. **Log what changed, not what did not.** `timed out: False` 178 times is noise; the five `True` entries are the signal, and they are buried. +## The Micromate's USB host supports FTDI and CDC-ACM only — not Prolific + +**Established 2026-09-25**, by isolating a bench modem link layer by layer and +then confirming against both firmware images. + +### The USB-A port is a HOST port with a fixed driver set + +``` +usbHostDelete_CDCACM CDC-ACM — the standard USB-serial class +usbHostDelete_FTDI FTDI chips (FTDISER handlers, set_line_coding, etc.) +usbHostDelete_MFS mass storage (thumb drives) +usbHostDelete_PRINTER printers +usbHostDelete_HUB / HC / USBH +``` + +**Identical in `11.0CB` and `11.0BD`** — 11 `FTDISER` strings and 22 `CDCACM` +strings in each, and **zero** matches for `prolific`, `pl2303`, `cp210`, `ch34` +or `silabs` in either. The firmware line makes no difference here. + +⚠ **So a Prolific PL2303 cable (VID `067b`) cannot work with a Micromate.** The +unit has no driver, never enumerates the device, and the serial path simply does +not exist. Use an **FTDI** cable (VID `0403`) or a CDC-ACM one. + +### How this was isolated — the method is reusable + +A Micromate on an RX55 was unreachable from THOR. Rather than guess, each layer +was eliminated in turn with `bridges/mm_probe.py`: + +| layer | test | result | +|---|---|---| +| unit's public IP | static APN `mw01.VZWSTATIC` | ruled out | +| firewall / trusted IPs | probe from a whitelisted source | **TCP connect ok, 268 ms** | +| network path | same | ruled out | +| baud | 115200 both ends | ruled out | +| unit modem-type setting | set back to `generic` | ruled out | +| THOR | probe bypasses it entirely and still failed | ruled out | +| **modem TCP→serial** | swapped the unit for a Linux box on the same cable | **POLL arrived byte-perfect** | +| **modem serial→TCP** | that box answered with a canned POLL reply | **round trip in 700 ms** | + +The last two are the decisive pair. With a laptop standing in for the unit on +the *same cable and modem*, the full round trip works — so the modem, the PAD +configuration, the firewall and the network are all clean, and the only element +that changed is what sits at the end of the cable. + +`scratch/fake_unit.py`-style substitution is worth remembering: **replace the +device with something known-good and re-run the same test.** It converts "the +unit is not answering" into "the unit is not receiving", which are very +different problems. + +⚠ This explains the **bench** setup conclusively. Whether it explains the +2026-09-22 field outage is **not** established — that unit reportedly worked at +first and degraded, which a wrong cable would not do. Treat them as separate +until the field unit's cable is identified. + ## Known-good RV55 config for a Micromate (2026-09-25) Read off a unit **deployed and working for months**. This is the reference to diff --git a/scratch/fake_unit.py b/scratch/fake_unit.py new file mode 100644 index 0000000..ec0c04d --- /dev/null +++ b/scratch/fake_unit.py @@ -0,0 +1,33 @@ +"""Pretend to be a Micromate on a serial port: log what arrives, reply to POLL. + +Proves the modem's return path (serial -> TCP) independently of the real unit. +""" +import os, select, sys, termios, time + +path, baud = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 115200 +B = {9600: termios.B9600, 38400: termios.B38400, 115200: termios.B115200}[baud] +fd = os.open(path, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) +a = termios.tcgetattr(fd) +a[0] = a[1] = a[3] = 0 +a[2] = termios.CS8 | termios.CREAD | termios.CLOCAL +a[4] = a[5] = B +a[6] = list(a[6]); a[6][termios.VMIN] = 0; a[6][termios.VTIME] = 0 +termios.tcsetattr(fd, termios.TCSANOW, a) +termios.tcflush(fd, termios.TCIOFLUSH) + +# A real POLL probe reply, captured from UM12947 on 2026-09-24. +REPLY = bytes.fromhex("0200c5a4000000000000300000000000000099") + b"\x03" + +print(f"fake unit on {path} @ {baud}; will answer any inbound frame", flush=True) +while True: + r, _, _ = select.select([fd], [], [], 1.0) + if not r: + continue + data = os.read(fd, 4096) + if not data: + continue + ts = time.strftime("%H:%M:%S") + print(f"{ts} IN {len(data):3} B {data.hex(' ')}", flush=True) + time.sleep(0.02) + os.write(fd, REPLY) + print(f"{ts} OUT {len(REPLY):3} B {REPLY.hex(' ')} <- canned POLL reply", flush=True)