#!/bin/sh
# luci-app-cellular 命令行工具
# 用法: cellular <子命令> [参数]
#   scan / list / info           重新扫描模组 / 列出模组 / 打印全部状态(JSON)
#   at <端口> <指令>             发一条 AT 指令
#   queue / atq                  查看 AT 队列状态 / 前台运行一个队列 worker
#   simscan / flight / usbreset  重新识别 SIM 卡 / 飞行模式 / 让主机重新枚举模组
#   copsscan                     搜索可用运营商
#   band / bandsync              查看频段 / 按保存的配置恢复频段
#   pref / prefsync              查看或设置驻网优先级 / 按保存的配置恢复
#   qos                          打印 QCI / 5QI / 签约速率相关指令的原始应答
#   sms <list|thread|status|sync|send> / smsarm   短信相关
#   wd [test] / netopt           网络看门狗 / 网络优选
#   logo / illu / latency        运营商图标 / 拨号说明插图 / 延迟测试
#   log / dial <apply|reset> / diag / purge   日志 / 拨号 / 环境自检 / 清理生成的接口
# 其余子命令由守护进程与 rpcd 在后台调用。

. /usr/share/cellular/lib.sh
. /usr/share/cellular/detect.sh
. /usr/share/cellular/info.sh
. /usr/share/cellular/dial.sh
. /usr/share/cellular/sms.sh
. /usr/share/cellular/watchdog.sh
. /usr/share/cellular/netopt.sh
. /usr/share/cellular/logo.sh

case "$1" in
	scan)  cellular_scan ;;
	list)  cellular_modems ;;
	info)  cellular_collect_all >/dev/null 2>&1; cellular_info_all; echo ;;
	at)
		[ -n "$2" ] && [ -n "$3" ] || { echo "用法: cellular at /dev/ttyUSB2 'AT+CGMM'"; exit 1; }
		shift; port="$1"; shift
		at_prio 1 20000
		at_cmd "$port" "$*" 3000
		;;
	queue)
		if atq_sup_alive; then
			echo "supervisor: 在跑"
		else
			echo "supervisor: 没在跑 —— 没有人给串口拉 worker"
			echo "            /etc/init.d/cellular restart  可以把它拉起来"
		fi
		_n=0
		for _p in $(atq_worker_ports); do
			_n=$(( _n + 1 ))
			printf 'worker: %-16s pid %-7s 排队 %s 条\n' \
				"$_p" "$(atq_worker_pid "$_p")" "$(atq_depth "$_p")"
		done
		[ "$_n" = "0" ] && echo "worker: 一个都没在跑 —— AT 会降级成直连(可能与后台采集抢串口)"
		echo "可用 AT 口: $(atq_ports_wanted | tr '\n' ' ')"
		echo "主动上报(URC): $(urc_enabled && echo 已开启 || echo 已关闭)"
		echo "排队中合计: $(atq_depth) 条"
		if at_busy; then
			echo "独占中: $(at_busy_what) (pid $(at_busy_pid)) —— 这期间只服务它自己的指令"
		else
			echo "独占中: 无"
		fi
		echo "收发方式: $(cfg_get at_transport auto)   单条超时: $(cfg_get at_timeout 1200) ms"
		if watch_get; then
			echo "界面在看: $WATCH_VIEW ($WATCH_AGE 秒前)"
		else
			echo "界面在看: 没有人 —— 后台不发 AT(除非开了「始终监听」)"
		fi
		;;
	atq)
		if [ -n "$2" ]; then atq_serve "$2"; else atq_supervise; fi
		;;
	simscan)
		id="$2"; mode="${3:-safe}"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_field "$id" at_port)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		echo "正在对 $p 重新识别 SIM 卡 (${mode} 模式) ..."
		at_busy_set 40 simrescan
		drv_sim_rescan "$p" "$mode" && echo "SIM 卡已就绪" || echo "未检测到 SIM 卡"
		at_busy_clear
		touch "$CELL_RUN/rescan"
		;;
	usbreset)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id 2>/dev/null)
		echo "正在复位 USB (${3:-auto}) ..."
		R=$(cellular_usb_reset "$id" "${3:-auto}")
		case "$R" in
			soft) echo "已重新枚举模组的 USB 设备" ;;
			hard) echo "已复位 USB 主机控制器" ;;
			needhard) echo "设备节点已消失, 需要复位整个 USB 控制器: cellular usbreset '' hard" ;;
			nobus) echo "找不到 USB 总线" ;;
			*) echo "复位失败(可能是 sysfs 不可写, 或该平台不支持)" ;;
		esac
		sleep 2
		cellular_scan >/dev/null 2>&1
		cellular_modems
		echo
		;;
	simreattach|reattach)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		trap 'at_busy_clear' EXIT INT TERM
		at_busy_set 40 simreattach
		_sim_reattach "$p"
		if [ "$1" = "reattach" ]; then
			if [ -n "$3" ] && drv_pref_get "$p" >/dev/null 2>&1; then
				pref_want_load "$id" >/dev/null 2>&1
				pref_cache_put "$id" 2>/dev/null
				if [ "$(pref_key "$PREF")" != "$3" ]; then
					cell_event cfg "模组没有接受「$(pref_cn "$3")」(现在是 ${PREF_CN:-$PREF}), 后台会按保存的配置继续补"
					mkdir -p "$CELL_RUN" 2>/dev/null
					: > "$CELL_RUN/preffix_$id" 2>/dev/null
				fi
			fi
			cell_event cfg "已重新注册网络(频段/制式变更后)"
		elif at_cmd "$p" "AT+CPIN?" 2000 | grep -q "READY"; then
			cell_event cfg "卡槽切换完成, SIM 卡已就绪"
		else
			cell_event cfg "卡槽切换完成, 但这个卡槽暂时读不到卡"
		fi
		at_busy_clear
		if [ "$1" = "simreattach" ]; then
			rm -f "$CELL_RUN"/slow_*.env 2>/dev/null
		fi
		: > "$CELL_RUN/collect_now" 2>/dev/null
		redial_schedule 8 "$id"
		;;
	modereboot)
		id="$2"; p="$3"; want="$4"
		[ -z "$id" ] && id=$(modem_field "" id)
		[ -c "$p" ] || p=$(modem_at_port_use "$id")
		[ -c "$p" ] || exit 1
		trap 'at_busy_clear' EXIT INT TERM
		at_prio 0 25000
		at_busy_set 60 usbmode
		if [ -n "$want" ]; then
			if drv_usbmode_now "$p" "$id" && [ "$USBMODE_NOW" != "$want" ]; then
				cell_event cfg "网卡模式还没写进模组(读回来是 $USBMODE_NOW), 重新写一次 $want"
				drv_usbmode_set "$p" "$want" "$id" >/dev/null 2>&1
				sleep 1
			fi
			sleep 2
		fi
		drv_reset "$p" >/dev/null 2>&1
		at_busy_clear
		if [ -s "$CELL_RUN/modesw_$id" ]; then
			_st=stay; _i=0
			while [ "$_i" -lt 20 ]; do
				[ -c "$p" ] || { _st=gone; break; }
				sleep 1; _i=$(( _i + 1 ))
			done
			read -r _w _a _o _t _x < "$CELL_RUN/modesw_$id" 2>/dev/null
			[ -n "$_w" ] && printf '%s %s %s %s %s\n' "$_w" "${_a:-1}" "${_o:--}" "${_t:-0}" "$_st" \
				> "$CELL_RUN/modesw_$id" 2>/dev/null
		fi
		: > "$CELL_RUN/rescan" 2>/dev/null
		cell_event cfg "网卡模式切换: 模组正在重启, 重新枚举后会自动重拨"
		redial_schedule 45 "$id"
		;;
	siminit)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		_nm=$(modem_field "$id" name 2>/dev/null)
		_sim_need_slot_init "$_nm" || exit 0
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		at_busy && exit 0
		cool_ok "siminit_$id" 120 || exit 0
		_n=$(cat "$CELL_RUN/siminit_n_$id" 2>/dev/null); is_num "$_n" || _n=0
		[ "$_n" -lt 6 ] || exit 0
		cool_mark "siminit_$id"
		trap 'at_busy_clear' EXIT INT TERM
		at_prio 1 20000
		at_busy_set 60 siminit
		drv_sim_slot_init "$p"
		rc=$?
		at_busy_clear
		case "$rc" in
			0)
				echo $(( _n + 1 )) > "$CELL_RUN/siminit_n_$id"
				cell_event cfg "SIM 卡槽已初始化(AT^SCICHG=0,1), SIM 卡已就绪"
				: > "$CELL_RUN/collect_now" 2>/dev/null
				redial_schedule 10 "$id"
				echo "SIM 卡已就绪"
				;;
			1)
				echo $(( _n + 1 )) > "$CELL_RUN/siminit_n_$id"
				cell_event down "已补发 AT^SCICHG=0,1 初始化 SIM 卡槽, 仍读不到 SIM 卡(请检查卡是否插好)"
				echo "仍读不到 SIM 卡"
				;;
			*) echo "SIM 卡状态正常, 不需要初始化" ;;
		esac
		;;
	regretry)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		at_busy && exit 0
		grep -q '"flight":1' "$CELL_RUN/info_$id.json" 2>/dev/null && exit 0
		trap 'at_busy_clear' EXIT INT TERM
		at_prio 1 25000
		at_busy_set 120 regretry
		drv_net_reselect "$p"
		at_busy_clear
		_i=0; _ok=0
		while [ "$_i" -lt 10 ]; do
			sleep 3
			_st=$(_reg_stat "$p" "AT+CEREG?" "+CEREG")
			case "$_st" in 1|5) _ok=1; break ;; 3) break ;; esac
			if [ ! -f "$CELL_RUN/no5greg$(echo "$p" | tr '/' '_')" ]; then
				_st=$(_reg_stat "$p" "AT+C5GREG?" "+C5GREG")
				case "$_st" in 1|5) _ok=1; break ;; 3) break ;; esac
			fi
			_i=$(( _i + 1 ))
		done
		if [ "$_ok" = "1" ]; then
			_rej_drop_reg "$id"
			cell_event up "重新选网后已注册到网络"
		fi
		: > "$CELL_RUN/collect_now" 2>/dev/null
		redial_schedule 5 "$id"
		;;
	qos)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		at_prio 1 20000
		_cid=$(uci -q get cellular.@dial[0].pdp_index 2>/dev/null); is_num "$_cid" || _cid=1
		_rat=$(sed -n 's/.*"rat":"\([^"]*\)".*/\1/p' "$CELL_RUN/info_$id.json" 2>/dev/null | head -n1)
		echo "模组    : $(modem_field "$id" name 2>/dev/null)  AT 口 $p"
		echo "当前制式: ${_rat:-未知}  ($(rat_class "$_rat" | sed -e 's/^eps$/EPS: 4G \/ 5G NSA, 读 QCI/' -e 's/^5gs$/5GS: 5G SA, 读 5QI/'))   拨号用的 PDP 序号: $_cid"
		echo "--- 原始应答 ---"
		for _c in "AT+C5GQOSRDP=$_cid" "AT+C5GQOSRDP" "AT+CGEQOSRDP=$_cid" "AT+CGEQOSRDP" "AT+CGEQOS?" \
		          'AT+QNWCFG="nr5g_ambr"' 'AT+QNWCFG="lte_ambr"'; do
			echo "> $_c"
			at_cmd "$p" "$_c" 1500 | sed 's/^/    /'
		done
		echo "--- 插件的解析 ---"
		if drv_qos "$p" "$_cid" "" "$_rat"; then
			echo "QCI / 5QI : $QCI  ($QCI_CN)  来源 $QCI_SRC"
		elif [ "$QCI_SRC" = "-" ]; then
			echo "QCI / 5QI : 五条标准指令全被模组拒绝(这颗模组的固件不提供这一项)"
		else
			echo "QCI / 5QI : 这一刻没读到 —— 5G SA 下高通平台的模组没有读 5QI 的指令, 属于固件限制;"
			echo "            驻在 4G / 5G NSA 上时再读, 或者在界面 QCI 那一格点「读取」(临时切 4G 读一次)"
		fi
		_apn=$(sed -n "s/^APN='\(.*\)'$/\1/p" "$CELL_RUN/slow_$id.env" 2>/dev/null | tail -n1)
		if [ -n "$QOS_AMBR_DL" ]; then
			echo "签约速率  : 下行 $QOS_AMBR_DL kbps / 上行 $QOS_AMBR_UL kbps  (随 RDP 应答给出)"
		elif drv_ambr_run "$p" "" "$_rat" "$_apn"; then
			echo "签约速率  : 下行 $AMBR_DL kbps / 上行 $AMBR_UL kbps  (${AMBR_APN:-默认承载}, 来源 $AMBR_SRC)"
		elif [ "$AMBR_SRC" = "-" ]; then
			echo "签约速率  : 模组不认 AT+QNWCFG 那两条(非移远模组), 读不到"
		else
			echo "签约速率  : 这一刻没读到(拨号连上之后才有)"
		fi
		if qos_mem_load "$id"; then
			[ -n "$QM_QCI" ] && echo "路由器记着: QCI $QM_QCI (在 $([ "$QM_RAT" = "5gs" ] && echo '5G SA' || echo '4G / NSA') 下读到, $(date -d "@$QM_TS" '+%Y-%m-%d %H:%M' 2>/dev/null || echo "$QM_TS"))"
			[ -n "$QM_RESTORE" ] && echo "★ 有一次「读取 QCI」没来得及切回: 待还原的优先级 = $QM_RESTORE (cellular qcirestore 可以立刻还原)"
		fi
		;;
	qciprobe)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		if [ ! -c "$p" ]; then
			qci_probe_note "$id" fail - "这一刻拿不到模组的 AT 串口, 没有改动任何设置"
			netchg_clear "$id"
			exit 1
		fi
		run_lock qciprobe 0 || exit 0
		# 中途被杀(Ctrl-C / 服务重启): 记号还在就当场切回去; 掉电那种来不及的由
		# cellulard 下次起来按 flash 上的记号补(cellular qcirestore)。
		_qp_bail() {
			if [ -s "$CELL_RUN/qciprobe_$id" ]; then
				read -r _t _pp _k _st < "$CELL_RUN/qciprobe_$id" 2>/dev/null
				[ "$_pp" = "$$" ] && qci_probe_restore "$id" "$p" "$_k" "" >/dev/null 2>&1
			fi
			[ "$(at_busy_pid)" = "$$" ] && at_busy_clear
			run_unlock qciprobe
		}
		trap '_qp_bail' EXIT INT TERM
		qci_probe_run "$id" "$p"
		rc=$?
		[ -n "$3" ] || { qci_probe_state "$id"; echo "${QPR_MSG:-完成}"; }
		exit $rc
		;;
	qcirestore)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		qos_mem_load "$id"
		[ -n "$QM_RESTORE" ] || { rm -f "$CELL_RUN/qciprobe_$id" 2>/dev/null; exit 0; }
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		at_busy && exit 0
		run_lock qciprobe 0 || exit 0
		trap 'at_busy_clear; run_unlock qciprobe' EXIT INT TERM
		at_prio 0 25000
		at_busy_set 60 qcirestore
		if drv_pref_get "$p" && [ "$(pref_key "$PREF")" = "$QM_RESTORE" ]; then
			qos_restore_clear "$id"
			rm -f "$CELL_RUN/qciprobe_$id" 2>/dev/null
			at_busy_clear
			echo "驻网优先级已经是 $(pref_cn "$QM_RESTORE"), 无需改动"
			exit 0
		fi
		if qci_probe_restore "$id" "$p" "$QM_RESTORE" "$PREF_SRC"; then
			netchg_set "$id" 60 pref "正在切回原来的驻网优先级并重新注册"
			at_busy_clear
			cell_event cfg "上一次「读取 QCI」没来得及切回, 已把驻网优先级还原为「$(pref_cn "$QM_RESTORE")」"
			: > "$CELL_RUN/collect_now" 2>/dev/null
			redial_schedule 15 "$id"
			echo "已还原为 $(pref_cn "$QM_RESTORE")"
		else
			at_busy_clear
			echo "还原失败(模组不接受这条指令), 稍后会再试"
			exit 1
		fi
		;;
	bandsync)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		band_lock_load "$id" || exit 0
		[ "$BANDLOCK_EN" = "1" ] || exit 0
		trap 'at_busy_clear' EXIT INT TERM
		band_lock_restore "$p" "$id"
		rc=$?
		rm -f "$CELL_RUN/bandfix_$id" 2>/dev/null
		if [ "$rc" = "0" ]; then
			band_cache_put "$id" 2>/dev/null
			netchg_set "$id" 60 band "正在按保存的频段配置重新搜索并驻网"
			: > "$CELL_RUN/collect_now" 2>/dev/null
			redial_schedule 25 "$id"
			echo "频段已按保存的配置恢复"
		elif [ "$rc" = "2" ]; then
			echo "频段恢复失败"
		else
			echo "频段与保存的配置一致, 无需改动"
		fi
		;;
	prefsync)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id" 2>/dev/null)
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		pref_want_load "$id" || exit 0
		[ "$PREFWANT_EN" = "1" ] || exit 0
		cool_ok prefsync 240 || exit 0
		cool_mark prefsync
		trap 'at_busy_clear' EXIT INT TERM
		at_prio 0 25000
		at_busy_set 60 prefsync
		if drv_pref_get "$p" && [ "$(pref_key "$PREF")" = "$PREFWANT" ]; then
			at_busy_clear
			echo "优先级与保存的一致, 无需改动"
			exit 0
		fi
		if drv_pref_set "$p" "$PREFWANT" "$PREF_SRC"; then
			cell_event cfg "模组的驻网优先级被改回了 $(pref_key "$PREF"), 已按保存的「$(pref_cn "$PREFWANT")」恢复"
			_sim_reattach "$p"
			drv_pref_get "$p" >/dev/null 2>&1
			netchg_set "$id" 60 pref "正在按保存的优先级重新搜索并驻网"
			at_busy_clear
			pref_cache_put "$id" 2>/dev/null
			rm -f "$CELL_RUN/preffix_$id" 2>/dev/null
			: > "$CELL_RUN/collect_now" 2>/dev/null
			redial_schedule 20 "$id"
			echo "优先级已恢复为 $(pref_cn "$PREFWANT")"
		else
			at_busy_clear
			echo "优先级恢复失败(模组不接受这条指令)"
		fi
		;;
	pref)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_field "$id" at_port)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		at_prio 1 20000
		if [ -n "$3" ]; then
			trap 'at_busy_clear' EXIT INT TERM
			at_busy_set 60 prefset
			drv_pref_set "$p" "$3" "$(sed -n "s/^PREF_SRC='\(.*\)'$/\1/p" "$CELL_RUN/slow_$id.env" 2>/dev/null | tail -n1)" || { at_busy_clear; echo "模组不接受这条指令"; exit 1; }
			pref_want_save "$id" "$3"
			_sim_reattach "$p"
			drv_pref_get "$p" >/dev/null 2>&1
			at_busy_clear
			pref_cache_put "$id" 2>/dev/null
			: > "$CELL_RUN/collect_now" 2>/dev/null
			redial_schedule 20 "$id"
		else
			drv_pref_get "$p" >/dev/null 2>&1
		fi
		echo "模组当前: $PREF ($(pref_cn "$(pref_key "$PREF")"))"
		if pref_want_load "$id"; then
			echo "已保存  : $PREFWANT ($(pref_cn "$PREFWANT"))"
			pref_drift "$id" "$PREF" && echo "比对结果: **不一致**, cellular prefsync 可以恢复" \
			                         || echo "比对结果: 一致"
		else
			echo "已保存  : 无(还没在界面上选过)"
		fi
		;;
	band)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_field "$id" at_port)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		at_prio 1 20000
		if drv_band_get "$p" ""; then
			echo "读取方式: $BAND_SRC   可在线修改: $([ "$BAND_SET" = "1" ] && echo 是 || echo 否)"
			echo "模组当前: LTE=[$BAND_LTE] NR=[$BAND_NR] NSA=[$BAND_NSA]"
		else
			echo "读不到频段(这颗模组没有上报频段列表)"
		fi
		if band_lock_load "$id"; then
			echo "已保存  : LTE=[$BANDLOCK_LTE] NR=[$BANDLOCK_NR] NSA=[$BANDLOCK_NSA]"
			echo "开机恢复: $([ "$BANDLOCK_EN" = "1" ] && echo 开启 || echo 关闭)"
			band_lock_drift "$id" && echo "比对结果: 一致" || echo "比对结果: **不一致**, cellular bandsync 可以恢复"
		else
			echo "已保存  : 无(还没在界面上点过「应用」)"
		fi
		;;
	redial)
		id="$2"
		[ -z "$id" ] && id=$(uci -q get cellular.global.active_modem)
		_ml=$(modem_id_live "$id" 2>/dev/null); [ -n "$_ml" ] && id="$_ml"
		[ -z "$id" ] && id=$(modem_field "" id)
		run_lock redial 20 || exit 0
		trap 'run_unlock redial' EXIT INT TERM
		if [ "$(dial_count_for "$id")" = "0" ]; then
			if dial_default_ensure "$id"; then
				cell_event cfg "没有拨号配置, 已为模组 ${id:-默认} 创建一条默认拨号并开始拨号"
			else
				cell_event down "重新拨号: 找不到可用的模组, 无法创建拨号配置"
				exit 1
			fi
		else
			DIAL_FORCE=1
			dial_ensure_all >/dev/null 2>&1
			dial_redial "$id" >/dev/null 2>&1 || dial_redial "" >/dev/null 2>&1
		fi
		: > "$CELL_RUN/collect_now" 2>/dev/null
		;;
	smssync)
		sms_enabled || exit 0
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(sms_at_port_any "$id")
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		run_lock smssync 25 || exit 0
		trap 'at_busy_clear; run_unlock smssync' EXIT INT TERM
		at_prio "${AT_PRIO:-5}" 30000
		at_busy_set 60 smssync
		n=$(sms_sync_port "$p")
		at_busy_clear
		run_unlock smssync
		rm -f "$CELL_RUN/sms_now" 2>/dev/null
		[ -n "$2" ] || echo "已同步 ${n:-0} 条"
		;;
	smsarm)
		sms_enabled || exit 0
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(sms_at_port_any "$id")
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		at_busy && exit 0
		trap 'at_busy_clear' EXIT INT TERM
		at_prio "${AT_PRIO:-5}" 20000
		at_busy_set 25 smsarm
		sms_arm_now "$p"
		rc=$?
		at_busy_clear
		[ -n "$2" ] || {
			if [ "$rc" = "0" ]; then echo "已武装: AT+CNMI=$SMS_CNMI_MODE (口 $p)"
			else echo "武装失败: ${SMS_CNMI_ERR:-无应答} (口 $p)"; fi
		}
		exit $rc
		;;
	logofetch)
		[ -n "$2" ] || { echo "用法: cellular logofetch <运营商名> [plmn]"; exit 1; }
		_lk=$(logo_key "$2" "$3" 2>/dev/null)
		[ -n "$_lk" ] || exit 1
		logo_lock_take "$_lk" || exit 0
		trap 'logo_lock_drop "'"$_lk"'"' EXIT INT TERM
		_lt=$(logo_fetch_retry "$2" "$3") && echo "已缓存 $_lk.$_lt" || echo "取不到 $_lk"
		logo_lock_drop "$_lk"
		;;
	sitefetch)
		[ -n "$2" ] || { echo "用法: cellular sitefetch <站点键>"; exit 1; }
		site_key_ok "$2" || exit 1
		mkdir -p "$CELL_RUN" 2>/dev/null
		mkdir "$CELL_RUN/sitelk_$2" 2>/dev/null || exit 0
		trap 'rmdir "$CELL_RUN/sitelk_'"$2"'" 2>/dev/null' EXIT INT TERM
		_lt=$(site_fetch_key "$2") && echo "已缓存 $2.$_lt" || echo "取不到 $2"
		rmdir "$CELL_RUN/sitelk_$2" 2>/dev/null
		;;
	illufetch)
		illu_lock_take || exit 0
		trap 'illu_lock_drop' EXIT INT TERM
		_lt=$(illu_fetch_retry) && echo "已缓存 data.$_lt" || echo "取不到(退回文字说明)"
		illu_lock_drop
		;;
	illu)
		case "$2" in
			""|status) illu_status ;;
			fetch)     illu_clear; "$0" illufetch ;;
			clear)     illu_clear; echo "拨号说明插图缓存已清空" ;;
			*)         echo "用法: cellular illu {status|fetch|clear}" ;;
		esac
		;;
	logo)
		case "$2" in
			list)  logo_list ;;
			clear) logo_clear; _logo_paths; rm -rf "$LOGO_SITE_DIR" 2>/dev/null; illu_clear; echo "运营商 / 站点图标与拨号说明插图缓存已清空" ;;
			key)   logo_keys "$3" "$4" ;;
			get)   "$0" logofetch "$3" "$4" ;;
			*)     echo "用法: cellular logo {list|clear|key <名字> [plmn]|get <名字> [plmn]}" ;;
		esac
		;;
	smssend)
		[ -n "$2" ] || { echo "用法: cellular smssend <id>"; exit 1; }
		id="$3"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(sms_at_port_any "$id")
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		[ -c "$p" ] || exit 1
		trap 'at_busy_clear' EXIT INT TERM
		at_prio 0 30000
		at_busy_set 90 smssend
		sms_send_id "$p" "$2"
		rc=$?
		at_busy_clear
		exit $rc
		;;
	sms)
		case "$2" in
			list)   sms_json_threads; echo ;;
			thread) sms_json_thread "${3:-*all}" "${4:-400}"; echo ;;
			status) sms_json_status "$3"; echo ;;
			rules)  sms_json_rules; echo ;;
			sync)   "$0" smssync "$3" ;;
			diag)
				id="$3"
				[ -z "$id" ] && id=$(modem_field "" id)
				p=$(sms_at_port_any "$id")
				[ -c "$p" ] || p=$(modem_field "$id" at_port)
				[ -c "$p" ] || p=$(sms_at_port_any "$id")
				trap 'at_busy_clear' EXIT INT TERM
				at_prio 0 25000
				at_busy_set 20 smsdiag
				sms_diag_run "$p" >/dev/null 2>&1
				at_busy_clear
				sms_diag_load
				echo "结论    : $(sms_verdict_text "$SMS_D_VERDICT")"
				echo "AT 端口 : ${p:-无}"
				echo "模组支持: $([ "$SMS_D_CMGF" = "1" ] && echo 是 || echo 否)"
				echo "短信中心: ${SMS_D_SCA:-(空 —— 卡侧没开通短信业务)}"
				echo "SIM 状态: ${SMS_D_PIN:-读不到}"
				echo "注册状态: ${SMS_D_REG:-未知}"
				echo "模组存储: ${SMS_D_USED:-0} / ${SMS_D_TOTAL:-0}"
				;;
			send)
				[ -n "$3" ] && [ -n "$4" ] || { echo "用法: cellular sms send <号码> <UTF-16BE 十六进制正文>"; exit 1; }
				sid=$(sms_queue_out "$3" "$4")
				[ -n "$sid" ] || { echo "排队失败"; exit 1; }
				"$0" smssend "$sid"
				;;
			*) echo "用法: cellular sms {list|thread|send|status|rules|sync|diag}" ;;
		esac
		;;
	flight)
		id="$3"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_field "$id" at_port)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		case "$2" in
			on)  drv_flight_set "$p" 1 && echo "飞行模式已开启" || echo "设置失败" ;;
			off) drv_flight_set "$p" 0 && echo "飞行模式已关闭" || echo "设置失败" ;;
			*)   drv_flight_get "$p" && echo "CFUN=$CFUN_RAW  飞行模式: $([ "$FLIGHT" = "1" ] && echo 开启 || echo 关闭)" \
			     || echo "读取失败" ;;
		esac
		;;
	purge)
		n=0
		for i in $(uci -q show network | sed -n 's/^network\.\(cell[^.=]*\)=interface$/\1/p'); do
			echo "  删除接口 $i"
			ifdown "$i" >/dev/null 2>&1
			_fw_del "$i" >/dev/null 2>&1
			uci -q delete network."$i"
			n=$((n+1))
		done
		uci -q commit network
		uci -q commit firewall
		[ "$n" -gt 0 ] && /etc/init.d/network reload >/dev/null 2>&1
		echo "已清理 $n 个接口。配置文件 /etc/config/cellular 保留, 如需一并删除请手动 rm。"
		;;
	copsscan)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_use "$id")
		[ -c "$p" ] || p=$(modem_field "$id" at_port)
		if [ ! -c "$p" ]; then
			printf '{"running":0,"operators":[],"error":"%s","updated":"%s"}\n' \
				"当前模组没有可用的 AT 端口, 无法搜索运营商; 到「设备信息」页重新扫描模组后再试" \
				"$(date '+%Y-%m-%d %H:%M:%S')" > "$CELL_RUN/cops_$id.json" 2>/dev/null
			echo "找不到 AT 口"; exit 1
		fi
		_blk=$(cops_scan_blocker "$id")
		if [ -n "$_blk" ]; then
			printf '{"running":0,"operators":[],"error":"%s","updated":"%s"}\n' \
				"$_blk" "$(date '+%Y-%m-%d %H:%M:%S')" > "$CELL_RUN/cops_$id.json" 2>/dev/null
			echo "$_blk"; exit 1
		fi
		trap 'at_busy_clear; at_abort_clear "$p"' EXIT INT TERM
		drv_cops_scan "$p" "$CELL_RUN/cops_$id.json" "${3:-}"
		cat "$CELL_RUN/cops_$id.json"
		;;
	copsrecover)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		p=$(modem_at_port_any "$id" 2>/dev/null)
		[ -c "$p" ] || { echo "找不到 AT 口"; exit 1; }
		trap 'at_busy_clear' EXIT INT TERM
		drv_cops_recover "$p"
		;;
	fccunlock)
		id="$2"
		[ -z "$id" ] && id=$(modem_field "" id)
		fcc_unlock "$id" "${3:-auto}"
		rc=$?
		echo "$FCC_MSG"
		exit $rc
		;;
	latency)
		latency_probe "$2" "$3"
		printf 'ok=%s ms=%s method=%s dev=%s err=%s\n' "$LAT_OK" "$LAT_MS" "$LAT_METHOD" "$LAT_DEV" "$LAT_ERR"
		;;
	log)
		cellular_log_json "${2:-120}"; echo
		;;
	wd)
		wd_load
		echo "看门狗: $([ "$WD_EN" = "1" ] && echo 已开启 || echo 已关闭)"
		echo "探测方式: $WD_METHOD   目标: $WD_HOST$([ "$WD_METHOD" = "dns" ] && echo " (解析 $WD_NAME)")$([ "$WD_METHOD" = "tcp" ] && echo " :$WD_PORT")"
		echo "超时: ${WD_TIMEOUT}s   间隔: ${WD_INTERVAL}s   连续失败: ${WD_FAILS} 次   动作冷却: ${WD_COOL}s"
		echo "动作: 重新拨号=$([ "$WD_REDIAL" = 1 ] && echo 开 || echo 关)  切换基站=$([ "$WD_CELL" = 1 ] && echo 开 || echo 关)  软复位=$([ "$WD_RESET" = 1 ] && echo 开 || echo 关)"
		if wd_pick_dev; then
			echo "出口: $WD_IFACE ($WD_DEV $WD_SRC)"
		else
			echo "出口: 还没有拿到地址的拨号接口"
		fi
		wd_state_load
		echo "连续失败: ${WDS_FAIL:-0}   当前档位: ${WDS_LEVEL:-0}"
		echo "自本次开机累计处理: ${WDS_ACTN:-0} 次  (重新拨号 ${WDS_A1:-0} / 切换基站 ${WDS_A2:-0} / 软复位 ${WDS_A3:-0})"
		[ -s "$WD_HIST" ] && { echo "最近几次处理:"; tail -n 5 "$WD_HIST"; }
		[ -s "$WD_LAST" ] && echo "最近一次探测: $(cat "$WD_LAST")"
		if [ "$2" = "test" ]; then
			echo "--- 立刻探测一次 ---"
			if wd_probe; then echo "通  (${WD_RTT} ms)"; else echo "不通 (${WD_RTT} ms)"; fi
		fi
		;;
	wdact)
		trap 'at_busy_clear' EXIT INT TERM
		wd_do_action "$2" "$3"
		rc=$?
		at_busy_clear
		[ "$rc" = "0" ] && echo "已执行: $(wd_action_name "$3")" || echo "执行失败: $(wd_action_name "$3")"
		;;
	netopt)
		id="$2"
		[ -z "$id" ] && id=$(uci -q get cellular.global.active_modem 2>/dev/null)
		_ml=$(modem_id_live "$id" 2>/dev/null); [ -n "$_ml" ] && id="$_ml"
		[ -z "$id" ] && id=$(modem_field "" id)
		no_analyze "$id" >/dev/null 2>&1
		no_load
		echo "网络优选: $([ "$NO_EN" = "1" ] && echo 已启用 || echo 未启用)   每 ${NO_INTERVAL} 秒分析一次"
		echo "自动执行: $([ "$NO_AUTO" = "1" ] && echo 开 || echo 关)   允许写入模组配置(NV): $([ "$NO_NV" = "1" ] && echo 开 || echo 关)"
		echo "当前评分: ${NO_SCORE:-0} / 100"
		[ -s "$CELL_RUN/no_plan" ] && { echo "--- 分析结论 ---"; awk -F'\t' '{printf "[%s] %s\n    %s\n", $2, $4, $5}' "$CELL_RUN/no_plan"; }
		no_state_load
		echo "自本次开机累计优化: ${NOS_ACTN:-0} 次"
		;;
	noact)
		trap 'at_busy_clear' EXIT INT TERM
		no_do_action "$2" "$3" "$4"
		rc=$?
		at_busy_clear
		case "$rc" in
			0) echo "网络优选已执行: $3" ;;
			2) echo "这一项需要先打开「允许写入模组配置」" ;;
			3) echo "冷却中, 跳过" ;;
			4) echo "你在「频段」里手动锁过频段, 自动优选不会覆盖它(手动点「立即优化」才会)" ;;
			*) echo "网络优选执行失败: $3" ;;
		esac
		;;
	dial)
		case "$2" in
			apply) dial_apply_all; echo "已应用" ;;
			reset) dial_reset_all; echo "已复位" ;;
			status) dial_status_json; echo ;;
			*) echo "用法: cellular dial apply|reset|status" ;;
		esac
		;;
	diag)
		echo "== 内核模块 =="
		for m in option qmi_wwan cdc_mbim cdc_ncm cdc_ether rndis_host mhi_net; do
			if lsmod 2>/dev/null | grep -q "^$m "; then echo "  [OK] $m"; else echo "  [--] $m"; fi
		done
		echo "== 拨号工具 =="
		for b in uqmi umbim comgt udhcpc jsonfilter microcom; do
			if command -v "$b" >/dev/null 2>&1; then echo "  [OK] $b"; else echo "  [--] $b (缺失)"; fi
		done
		echo "== 串口 =="
		ls /dev/ttyUSB* /dev/ttyACM* /dev/cdc-wdm* /dev/wwan* 2>/dev/null | sed 's/^/  /'
		echo "== 模组 =="
		cellular_modems
		echo
		;;
	*)
		echo "用法: cellular {scan|list|info|at|queue|atq|simscan|simreattach|reattach|siminit|regretry|bandsync|band|qos|flight|usbreset|copsscan|fccunlock|latency|sms|smsarm|logo|illu|log|wd|dial|diag|purge}"
		exit 1
		;;
esac
