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)