#!/bin/sh # Cellular Modem Auto-Detector for OpenWrt # Outputs discovered modems in JSON format and updates /tmp/run/modem-manager/devices.json RUN_DIR="/tmp/run/modem-manager" CACHE_FILE="$RUN_DIR/devices.json" KNOWN_VENDORS="2c7c 2cb7 1e0e 12d1 19d2 1bbb 05c6 413c 0bdb 1199 2977 1508 1bc7 0e8d 1782 0489 1fac 2dee 33f8 3731 2df3 20a6" get_sys_attr() { local path="$1" if [ -f "$path" ]; then tr -d '\r\n' < "$path" 2>/dev/null fi } detect_all() { echo "{" echo ' "devices": [' local FIRST=1 for dev in /sys/bus/usb/devices/*; do [ -d "$dev" ] || continue [ -f "$dev/idVendor" ] || continue [ -f "$dev/idProduct" ] || continue local VID=$(get_sys_attr "$dev/idVendor") local PID=$(get_sys_attr "$dev/idProduct") local DEVCLASS=$(get_sys_attr "$dev/bDeviceClass") case "$VID" in 1d6b|2109|1d50) continue ;; esac [ "$DEVCLASS" = "09" ] && continue local IS_MODEM=0 for kv in $KNOWN_VENDORS; do if [ "$VID" = "$kv" ]; then IS_MODEM=1 break fi done local DEVNAME=$(basename "$dev") # Direct sysfs tree discovery for serial ports under this USB device local TTYS="" for t in $(find "$dev" -maxdepth 5 \( -name "ttyUSB*" -o -name "ttyACM*" \) 2>/dev/null); do local b=$(basename "$t") case "$b" in ttyUSB*|ttyACM*) if [ -e "/dev/$b" ]; then case " $TTYS " in *" /dev/$b "*) ;; *) TTYS="$TTYS /dev/$b" ;; esac fi ;; esac done # Fallback tty search via /sys/class/tty if [ -z "$TTYS" ]; then for t in /sys/class/tty/ttyUSB* /sys/class/tty/ttyACM*; do [ -e "$t" ] || continue local t_target=$(readlink "$t" 2>/dev/null || readlink -f "$t" 2>/dev/null) case "$t_target" in *"$DEVNAME"*) local b=$(basename "$t") [ -e "/dev/$b" ] && case " $TTYS " in *" /dev/$b "*) ;; *) TTYS="$TTYS /dev/$b" ;; esac ;; esac done fi TTYS=$(echo "$TTYS" | sed 's/^ *//') # Network interfaces discovery under this USB device local NETS="" for n in $(find "$dev" -maxdepth 5 -name "net" 2>/dev/null); do [ -d "$n" ] || continue for iface in "$n"/*; do [ -d "$iface" ] || continue local b=$(basename "$iface") case "$b" in lo|br-*|eth*|lan*|wan*|sfp*) ;; *) case " $NETS " in *" $b "*) ;; *) NETS="$NETS $b" ;; esac ;; esac done done # Fallback net search via /sys/class/net if [ -z "$NETS" ]; then for n in /sys/class/net/*; do [ -d "$n" ] || continue local n_target=$(readlink "$n" 2>/dev/null || readlink -f "$n" 2>/dev/null) case "$n_target" in *"$DEVNAME"*) local b=$(basename "$n") case "$b" in lo|br-*|eth*|lan*|wan*|sfp*) ;; *) case " $NETS " in *" $b "*) ;; *) NETS="$NETS $b" ;; esac ;; esac ;; esac done fi NETS=$(echo "$NETS" | sed 's/^ *//') if [ -n "$TTYS" ] || [ -n "$NETS" ] || [ $IS_MODEM -eq 1 ]; then IS_MODEM=1 fi [ $IS_MODEM -eq 1 ] || continue local MFG=$(get_sys_attr "$dev/manufacturer") local PROD=$(get_sys_attr "$dev/product") local SER=$(get_sys_attr "$dev/serial") local DRIVER="option" for net_iface in $NETS; do if [ -e "/sys/class/net/$net_iface/device/driver" ]; then local drv_target=$(basename "$(readlink -f "/sys/class/net/$net_iface/device/driver" 2>/dev/null)") case "$drv_target" in cdc_ncm|cdc_ether|cdc_mbim|qmi_wwan) DRIVER="$drv_target" break ;; esac fi done local PRIMARY_TTY="" for t in $TTYS; do case "$t" in *ttyUSB2|*ttyACM0) PRIMARY_TTY="$t" break ;; esac done if [ -z "$PRIMARY_TTY" ]; then for t in $TTYS; do case "$t" in *ttyUSB3) PRIMARY_TTY="$t" break ;; esac done fi [ -z "$PRIMARY_TTY" ] && PRIMARY_TTY=$(echo "$TTYS" | awk '{print $1}') local PRIMARY_NET=$(echo "$NETS" | awk '{print $1}') [ $FIRST -eq 1 ] || echo "," FIRST=0 local TTY_JSON="" for t in $TTYS; do [ -n "$TTY_JSON" ] && TTY_JSON="$TTY_JSON, " TTY_JSON="$TTY_JSON\"$t\"" done local NET_JSON="" for n in $NETS; do [ -n "$NET_JSON" ] && NET_JSON="$NET_JSON, " NET_JSON="$NET_JSON\"$n\"" done local MODES_JSON="\"ncm\", \"ecm\"" cat < "${CACHE_FILE}.tmp" && mv -f "${CACHE_FILE}.tmp" "$CACHE_FILE" # Output to stdout if not purely --cache mode if [ "$1" != "--cache" ]; then echo "$OUTPUT" fi exit 0