diff --git a/failover/van-modem-usb-kick b/failover/van-modem-usb-kick deleted file mode 100755 index 00aa67e..0000000 --- a/failover/van-modem-usb-kick +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 -"""van-modem-usb-kick — recover the EC25 modem when it's missing after boot. - -Seen on 2026-08-04, right after moving the modem onto the new powered UGreen -hub: the 5GHz AP (rtw89, on the hub's outer USB chip) and the hub's built-in -Ethernet (AX88179, on its inner cascaded USB2.1 hub chip) both enumerate -cleanly at boot, but the EC25 modem — plugged into a port on that same inner -hub chip — sometimes never does, even though it has power (LED on). dmesg -shows no connect attempt at all for the modem's port during boot (contrast -the Ethernet's clean enumeration, or the AP's one-shot retry after a -descriptor-read error) — the port just never signalled a connect in time, and -a hub only rescans a port on an actual connect/disconnect edge, so it stays -invisible until something produces one. Physically unplugging/replugging -either the whole hub or just the modem fixes it immediately, because both -produce that edge. This script produces the same edge in software: unbind -then rebind the inner hub chip's kernel driver, which forces it to redo -enumeration of everything on it (the modem, and briefly the Starlink -Ethernet — the AP is on the outer chip and is undisturbed). - -The inner hub is found by walking up from STARLINK_IFACE's sysfs device (it's -the Ethernet's parent hub) rather than hardcoding a bus/port path, since that -identifies "the hub carrying the modem" without assuming a specific topology. - -Bounded to a few minutes after boot, then exits — this is a boot-race -backstop, not a permanent watcher (a modem physically removed later should -just stay gone, not get fought). - -Usage: van-modem-usb-kick [modem-usb-vendor] [starlink-iface] -Defaults to 2c7c (Quectel) and enx6c1ff7d210a5 — see deploy.conf. -""" - -import sys -import time -from pathlib import Path - -MODEM_VENDOR = sys.argv[1] if len(sys.argv) > 1 else "2c7c" -STARLINK_IFACE = sys.argv[2] if len(sys.argv) > 2 else "enx6c1ff7d210a5" -INTERVAL = 20 # seconds between checks -BUDGET_S = 6 * INTERVAL # ~2 minutes of retries after boot, then give up quietly -USB_DEVICES = Path("/sys/bus/usb/devices") -HUB_DRIVER = Path("/sys/bus/usb/drivers/hub") - - -def log(msg, level="info"): - # systemd journal severity prefixes (sd-daemon), same convention as van-ap-watchdog. - pri = {"info": "<6>", "warn": "<4>", "crit": "<2>"}.get(level, "<6>") - print(pri + msg, flush=True) - - -def modem_present(vendor): - for f in USB_DEVICES.glob("*/idVendor"): - try: - if f.read_text().strip() == vendor: - return True - except OSError: - continue - return False - - -def inner_hub_name(starlink_iface): - """sysfs driver-name (e.g. '2-1.4') of the USB hub carrying starlink_iface — - that's the same hub chip the modem's port is on. None if the Ethernet - itself hasn't enumerated (nothing to walk up from).""" - dev = Path(f"/sys/class/net/{starlink_iface}/device") - if not dev.exists(): - return None - # dev resolves to ...//./.:. - # (e.g. .../2-1.4/2-1.4.2/2-1.4.2:2.0) — two levels up is the hub itself. - hub = dev.resolve().parent.parent - if not (hub / "bDeviceClass").exists(): - return None - try: - if hub.joinpath("bDeviceClass").read_text().strip() != "09": # 09 = hub - return None - except OSError: - return None - return hub.name - - -def kick(hub_name): - log(f"modem (vendor {MODEM_VENDOR}) absent — rebinding hub {hub_name} to force " - f"re-enumeration (same effect as unplug/replug; briefly bounces {STARLINK_IFACE})", - "warn") - try: - (HUB_DRIVER / "unbind").write_text(hub_name) - time.sleep(2) - (HUB_DRIVER / "bind").write_text(hub_name) - except OSError as e: - log(f"hub rebind failed: {e}", "warn") - - -def main(): - log(f"van-modem-usb-kick up: watching for USB vendor {MODEM_VENDOR} for up to " - f"{BUDGET_S}s after boot") - elapsed = 0 - kicked = False - while elapsed < BUDGET_S: - if modem_present(MODEM_VENDOR): - log("modem present — exiting" + (" (recovered)" if kicked else "")) - return - if not kicked: - hub_name = inner_hub_name(STARLINK_IFACE) - if hub_name: - kick(hub_name) - kicked = True - # else: Ethernet itself isn't up yet — give the system more time - # before assuming anything is wrong. - time.sleep(INTERVAL) - elapsed += INTERVAL - if not modem_present(MODEM_VENDOR): - log(f"modem still absent after {BUDGET_S}s — giving up " - f"(leaving it for manual unplug/replug from here on)", "warn") - - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - sys.exit(0) diff --git a/failover/van-modem-usb-kick.service b/failover/van-modem-usb-kick.service deleted file mode 100644 index 3cf894f..0000000 --- a/failover/van-modem-usb-kick.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=EC25 modem boot-recovery backstop (kicks its USB hub if the modem never enumerated) -After=systemd-udev-settle.service -Wants=systemd-udev-settle.service - -[Service] -Type=simple -ExecStart=/usr/local/sbin/van-modem-usb-kick @MODEM_USB_VENDOR@ @STARLINK_IFACE@ - -[Install] -WantedBy=multi-user.target