#!/bin/sh # Alpine 5G Router – modem status via AT commands (AT_COMMANDS_REFERENCE.md) # Outputs JSON. Used by Web GUI /api/status. # Rev: 3 (see REVISION in repo root) CONFIG="/etc/5g-router.conf" AT_PORT="${AT_PORT:-/dev/ttyUSB1}" AT_LOCK="/var/lock/5g-at.lock" [ -f "$CONFIG" ] && . "$CONFIG" || true # Acquire exclusive lock on AT port to avoid conflicts with connect-5g.sh exec 9>"$AT_LOCK" flock -w 10 9 || { echo '{"error":"AT port busy"}'; exit 1; } get_at() { _cmd="$1" _wait="${2:-1}" timeout $((_wait + 2)) sh -c " [ -c \"$AT_PORT\" ] || exit 1 cat $AT_PORT 2>/dev/null & _p=\$! sleep 0.3 echo -e '${_cmd}\r' > $AT_PORT sleep $_wait kill \$_p 2>/dev/null " 2>/dev/null } json_str() { printf '"%s"' "$(echo "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\r//g' | tr '\n' ' ')"; } # Probe port: send AT, return 0 if we see OK try_at_port() { _port="$1" [ ! -c "$_port" ] && return 1 _out=$(timeout 4 sh -c " cat $_port 2>/dev/null & _p=\$! sleep 0.3 echo -e 'AT\r' > $_port sleep 1 kill \$_p 2>/dev/null " 2>/dev/null) echo "$_out" | grep -q 'OK' && return 0 || return 1 } # Use configured port if it exists and is a char device; else try common Fibocom AT ports if [ ! -c "$AT_PORT" ]; then for p in /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2; do if try_at_port "$p"; then AT_PORT="$p" break fi done fi [ ! -c "$AT_PORT" ] && echo '{}' && exit 0 # Disable command echo so we get only response lines (ATE0); avoids parsing "AT+CGMI" as manufacturer get_at "ATE0" 1 >/dev/null 2>&1 sleep 0.2 # Query AT commands (reference: AT_COMMANDS_REFERENCE.md) r_cgmi=$(get_at "AT+CGMI" 1) r_cgmm=$(get_at "AT+CGMM" 1) r_cgmr=$(get_at "AT+CGMR" 1) r_cgsn=$(get_at "AT+CGSN" 1) r_csq=$(get_at "AT+CSQ" 1) r_creg=$(get_at "AT+CREG?" 1) r_cereg=$(get_at "AT+CEREG?" 1) r_usbmode=$(get_at "AT+GTUSBMODE?" 1) r_ccid=$(get_at "AT+CCID" 1) r_cops=$(get_at "AT+COPS?" 1) r_cgpaddr=$(get_at "AT+CGPADDR=1" 1) # Parse (skip echoed command lines: lines starting with AT or +) manufacturer=$(echo "$r_cgmi" | grep -vE '^AT|^\+' | grep -oE '^[A-Za-z0-9 ].*' | head -1 | sed 's/\r$//; s/^ *//; s/ *$//') model=$(echo "$r_cgmm" | grep -vE '^AT|^\+' | grep -oE '^[A-Za-z0-9\-].*' | head -1 | sed 's/\r$//; s/^ *//; s/ *$//') revision=$(echo "$r_cgmr" | grep -vE '^(AT|OK|\+)' | head -1 | sed 's/\r$//; s/^ *//; s/ *$//') imei=$(echo "$r_cgsn" | grep -oE '[0-9]{15}' | head -1) csq=$(echo "$r_csq" | grep "+CSQ:" | grep -oE '[0-9]+' | head -1) creg=$(echo "$r_creg" | grep "+CREG:" | head -1) cereg=$(echo "$r_cereg" | grep "+CEREG:" | head -1) usb_mode=$(echo "$r_usbmode" | grep "+GTUSBMODE:" | grep -oE '[0-9]+' | head -1) iccid=$(echo "$r_ccid" | grep "+CCID:" | sed 's/+CCID:[[:space:]]*//; s/\r$//; s/"//g' | head -1) cops=$(echo "$r_cops" | grep "+COPS:" | sed 's/+COPS:[[:space:]]*//; s/\r$//' | head -1) modem_ip=$(echo "$r_cgpaddr" | grep "+CGPADDR:" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) # Registration: CREG 0,1 = registered home, 0,5 = registered roaming; CEREG same creg_status="" case "$creg" in *",1"*) creg_status="registered_home";; *",5"*) creg_status="registered_roaming";; *",2"*) creg_status="searching";; *",0"*) creg_status="not_registered";; *) creg_status="unknown";; esac cereg_status="" case "$cereg" in *",1"*) cereg_status="registered_home";; *",5"*) cereg_status="registered_roaming";; *",2"*) cereg_status="searching";; *",0"*) cereg_status="not_registered";; *) cereg_status="unknown";; esac # Operator name from +COPS: 0,0,"Vodafone",7 operator_name=$(echo "$cops" | sed -n 's/.*"\([^"]*\)".*/\1/p') # Build JSON (no jq) printf '{\n' printf ' "at_port": '; json_str "$AT_PORT"; printf ',\n' printf ' "manufacturer": '; json_str "${manufacturer:-}"; printf ',\n' printf ' "model": '; json_str "${model:-}"; printf ',\n' printf ' "revision": '; json_str "${revision:-}"; printf ',\n' printf ' "imei": '; json_str "${imei:-}"; printf ',\n' printf ' "signal_csq": %s,\n' "${csq:-null}" printf ' "creg_status": '; json_str "$creg_status"; printf ',\n' printf ' "cereg_status": '; json_str "$cereg_status"; printf ',\n' printf ' "usb_mode": '; json_str "${usb_mode:-}"; printf ',\n' printf ' "iccid": '; json_str "${iccid:-}"; printf ',\n' printf ' "operator": '; json_str "${operator_name:-}"; printf ',\n' printf ' "modem_ip": '; json_str "${modem_ip:-}"; printf '\n' printf '}\n'