av (Framework Desktop) — USB port allocation¶
Final plan from the 2026-07-08 session (several iterations); HDD addition 2026-07-11.
Living copy of ~/ha/av-usb-port-map.md on the laptop; this is the on-box copy.
Topology gotcha (drives the whole plan)¶
The two rear USB-A ports hang off ONE internal 2-port hub — they share a single bus pair (one 480M USB2 lane-set + one 10G USB3 lane-set). They are NOT independent ports. The two USB-C ports are each their own root port/bus.
USB2 (480M) and USB3 (SuperSpeed) traffic ride separate wire pairs through the same hub — a USB3 device does not contend with USB2 devices' bandwidth/schedule, only shares the physical hub.
Port map¶
| Port | Device | Why |
|---|---|---|
| USB-C #1 | Coral NPU | dedicated 10G bus; genuinely-5G-traffic device |
| USB-C #2 | Motu sound card | dedicated bus → clean isochronous schedule for realtime audio (no xruns) |
| USB-A #1 | powered USB 3.0 hub → ZBT-1 (Thread) + ZBT-2 (Zigbee) + 2× 2.4GHz controller dongles + GL.iNet Comet (KVM input) | all low-bandwidth; shared bus irrelevant; RF handled by placement (below) |
| USB-A #2 | powered external HDD | USB3 SS lanes ≠ radios' 480M lanes, so no contention despite shared internal hub |
RF placement rules (the part that actually matters)¶
- USB 3.0 connectors/cabling radiate 2.4 GHz noise (the canonical Zigbee-killer). Distance is the mitigation:
- ZBT-1 + ZBT-2 on ~1 m extension cables, stick ends ≥1 m from each other (Zigbee↔Thread mutual desense is stick-to-stick), away from the box, the Coral's cable, and the HDD + its cable.
- Controller dongles on short extensions with line-of-sight to the couch — RF placement dominates latency; USB2-vs-USB3 bus latency is sub-millisecond noise.
- Hub must be powered so bus power never browns out the radios.
Reasoning snapshots (from the session)¶
- Motu on the shared A-bus risked xruns from contested 480M isochronous scheduling → moved to its own USB-C root port.
- Controllers don't deserve USB-C ports: a 2.4 GHz controller frame is 1–8 ms; bus choice is noise. Both A ports share one bus anyway, so "one A port each" bought nothing.
- KVM (Comet) is backup/debug-grade → hub is fine.
- ZBT sticks on a USB 3.0 hub is fine given the extensions — earlier "must be USB 2.0 hub" advice was overweighted; separation is what matters.
Gotcha: ZBT-1 (SkyConnect) is marginal behind an UNPOWERED hub¶
The SkyConnect's OpenThread RCP link is unreliable behind an unpowered USB hub: otbr reaches
leader, then the RCP stops answering (HandleRcpTimeout … RadioSpinelNoResponse) and cycles
(spinel_driver Init() Failure). It's a power-starvation effect on the shared unpowered rail, and
the CP210x-bridged RCP is the sensitive victim (it's NOT the cable — same cable is flawless direct).
- Load makes it worse, but light isn't enough. With the 250 mA KVM also on the hub it dropped every ~72 s; with just the radios (~200 mA) it held several minutes but still dropped periodically. An unpowered hub is marginal regardless of load.
- Direct into a rear USB-A port = rock-solid (
lsusb -tpath1-1.1, indefinite, zero errors; an extension cable for RF distance is fine). Proven-reliable placement. - A powered hub (clean per-port rail) is the expected fix for running ZBT-1 on a hub, but is untested as of 2026-08-01 — confirm before relying on it.
- ZBT-2 (Zigbee, native-USB CDC) is unaffected on the same hub — specific to the CP210x RCP.
- The
tools/otbr-run.shpacing wrapper keeps a bad link a paced flap instead of spiralling into a reset-churn wedge (otbr-agent D-state, only a physical power-cycle recovers).
Recovery after moving the Zigbee coordinator (ZBT-2)¶
Moving the ZBT-2 to another USB port does not need any config change — the
zigbee2mqtt container binds it by serial (/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_…-if00),
so the path survives the move. But unplugging it makes z2m lose the adapter and exit,
and its restart: unless-stopped policy can't bring it back: Docker binds the device node
(major:minor) at container-create time and won't re-resolve a node that vanished during
the unplug. So the container just sits Exited and Home Assistant shows every Zigbee
device dead.
Self-heal: a udev rule (stacks/ha/99-zigbee2mqtt-hotplug.rules →
zigbee2mqtt-hotplug.service → tools/zigbee2mqtt-rebind.sh) fires when the coordinator
re-enumerates and recreates the container against the fresh node. If that isn't installed
or doesn't fire, recover manually:
--force-recreate (not a plain restart) is what re-resolves the by-id symlink to the
current device node.