diff --git a/bridges/mm_probe.py b/bridges/mm_probe.py index 8ba7992..e020a03 100644 --- a/bridges/mm_probe.py +++ b/bridges/mm_probe.py @@ -134,17 +134,30 @@ def probe(host: str, port: int, timeout: float) -> int: step(2, "POLL", f"NO REPLY in {timeout:.1f} s") print("\nverdict: the MODEM answered but the unit did not.") print(" TCP is fine end to end — something accepted the connection.") - print(" What is missing is the serial side. Most likely the modem is") - print(" not forwarding to its serial port, which is what a wedged") - print(" transparent-TCP session looks like: the slot is held by a") - print(" connection that never closed.") + print(" What is missing is the serial side. Two quite different") + print(" causes produce this, and they are NOT distinguishable from") + print(" here:") + print("\n 1. SOMEONE ELSE HOLDS THE SESSION. These modems bridge ONE") + print(" TCP session to serial at a time. A second connection is") + print(" accepted and then simply not forwarded. Confirmed 2026-09-26:") + print(" with THOR connected this probe saw exactly this; the moment") + print(" THOR disconnected the same probe returned the serial number.") + print(" ** Check whether THOR (or anything else) has the unit first. **") + print("\n 2. The serial path is genuinely broken — a stale session the") + print(" modem never released, a cable the unit cannot enumerate, or") + print(" a unit that is off.") print("\n Try, in order:") - print(" 1. ACEmanager -> TCP Idle Timeout. If 0/disabled, a stale") - print(" session holds the slot forever. 2 minutes is the value") - print(" this project standardised on.") - print(" 2. Reboot the modem. If that fixes it, the modem was") - print(" holding state and the timeout is the permanent fix.") - print(" 3. Check the unit's own screen — serial cable, power.") + print(" 1. Disconnect any other client and re-probe. If it answers,") + print(" it was contention, not a fault.") + print(" 2. The cable's chipset. A Micromate drives FTDI and CDC-ACM") + print(" only — a Prolific PL2303 gives it no serial port at all.") + print(" lsusb: FTDI is 0403, Prolific 067b.") + print(" 3. Power-cycle the UNIT with the cable attached (hold power") + print(" 5 s, through the two-stage prompt). Its USB host rescans") + print(" on cold boot; it may not on hot-swap.") + print(" 4. AirLink OS -> TCP Idle Timeout. If 0/disabled, a stale") + print(" session holds the slot indefinitely. 2 minutes is the") + print(" value this project standardised on.") sock.close() return 5 diff --git a/docs/micromate_protocol_reference.md b/docs/micromate_protocol_reference.md index 2ceb32d..452704c 100644 --- a/docs/micromate_protocol_reference.md +++ b/docs/micromate_protocol_reference.md @@ -2162,6 +2162,52 @@ unit.** No USB-to-serial adapter anywhere in the path, so there is no chipset t get wrong. That workaround has been quietly routing around this exact failure mode. +### ✅ RESOLVED (2026-09-26) — and it took a cold boot as well as the cable + +Swapping to a genuine FTDI cable (`lsusb`: `0403:6001`, FT232) **did not work on +its own.** A probe immediately after the swap returned the same *connected, no +reply*. + +**A cold boot of the unit was also required.** Holding the power button for five +seconds — through the two-stage prompt that disconnects the internal battery — +and powering back up **with the cable already attached**, brought it straight up. +THOR at the office connected the moment it finished booting, and an independent +probe returned `UM12947, idle` over cellular. + +⚠ **The USB host does not rescan on hot-swap.** The Micromate enumerates its +USB-A host port at boot; once it has failed to identify a device there it does +not appear to try again. Changing that cable is a power-cycle operation, and +anyone swapping one in the field who probes immediately will conclude the new +cable is faulty too. + +So the full chain for a Micromate on a modem is: + +1. an **FTDI** or CDC-ACM cable — a Prolific PL2303 gives the unit no serial port +2. a **cold boot** with that cable attached + +### ✅ These modems bridge ONE session at a time — confirmed + +Long suspected, never demonstrated. Demonstrated now, by accident: + +| | probe result | +|---|---| +| THOR connected to the unit | `TCP connect ok` … **`NO REPLY`** | +| THOR disconnected, nothing else changed | `reply 68 B`, **`UM12947`**, idle | + +The PAD **accepts** a second TCP connection and then does not forward it. It +does not refuse, and it does not close — it simply never bridges. + +⚠ **This means "connected but no reply" has two causes that are indistinguishable +from the client side:** a genuinely broken serial path, and *somebody else +already has the unit*. `bridges/mm_probe.py` originally reported only the first, +which would send a diagnosis in exactly the wrong direction; its verdict now +names both and tells you to eliminate contention first. + +**For SFM this is a design constraint, not a footnote.** Our receiver and THOR +cannot both hold a unit, and during any migration both will exist. "Another +client holds this unit" needs to be a distinct, visible state — not folded into +a failure, and certainly not into a green tick. + ### How this was isolated — the method is reusable A Micromate on an RX55 was unreachable from THOR. Rather than guess, each layer