#!/bin/sh
# luci-app-cellular 守护进程
# 按"界面正在看哪一页"调度采集, 并负责开机拨号、链路看护、网络看门狗、网络优选、
# 短信同步、信号历史清理等后台任务。

. /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

PIDFILE="/var/run/cellulard.pid"
RESCAN_FLAG="$CELL_RUN/rescan"
NOW_FLAG="$CELL_RUN/collect_now"
CELL_CLI="${CELL_CLI:-/usr/sbin/cellular}"

echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"; exit 0' TERM INT

cell_log "cellulard 启动"

cellular_scan >/dev/null 2>&1
rm -f "$CELL_RUN"/slow_*.env 2>/dev/null
rm -f "$CELL_RUN"/sigskip_* 2>/dev/null
rm -f "$CELL_RUN/bg_collect" "$CELL_RUN/no_bgreq" 2>/dev/null
command -v sms_push_rearm >/dev/null 2>&1 && sms_push_rearm

# ---- 开机自动拨号
boot_dial() {
	local i=0 need
	command -v dial_enabled_count >/dev/null 2>&1 || { sleep 8; dial_apply_all; return 0; }
	need=$(dial_enabled_count)
	[ "$need" -gt 0 ] 2>/dev/null || return 0
	while [ "$i" -lt 36 ]; do
		if [ "$(dial_ready_count)" -gt 0 ] 2>/dev/null; then
			sleep 2
			sim_boot_init
			fcc_boot_unlock
			dial_apply_all
			cell_event cfg "开机自动拨号已应用($need 条)"
			return 0
		fi
		i=$(( i + 1 ))
		sleep 5
		[ $(( i % 3 )) -eq 0 ] && : > "$CELL_RUN/rescan" 2>/dev/null
	done
	cell_event down "开机等了 3 分钟仍没有认到可用的模组, 拨号暂不应用; 认到之后会自动补上"
	return 1
}
# 开机自动 FCC 解锁。
fcc_boot_unlock() {
	local id
	[ "$(cfg_get fcc_auto 1)" = "1" ] || return 0
	command -v fcc_prone >/dev/null 2>&1 || return 0
	fcc_tool_ok || return 0
	for id in $(cellular_modem_ids 2>/dev/null); do
		mkdir "$CELL_RUN/fccboot_$id" 2>/dev/null || continue
		fcc_prone "$id" || continue
		cell_event sys "模组 $id 属于出厂带 FCC 锁的型号, 开机自动做一次解锁握手"
		"$CELL_CLI" fccunlock "$id" auto >/dev/null 2>&1
	done
	return 0
}
# 鼎桥 MT57xx 开机补一次 SIM 卡槽初始化。
sim_boot_init() {
	local id
	command -v _sim_need_slot_init >/dev/null 2>&1 || return 0
	for id in $(cellular_modem_ids 2>/dev/null); do
		_sim_need_slot_init "$(modem_field "$id" name 2>/dev/null)" || continue
		mkdir "$CELL_RUN/simboot_$id" 2>/dev/null || continue
		"$CELL_CLI" siminit "$id" boot >/dev/null 2>&1
	done
	return 0
}
if [ "$(cfg_get auto_dial 1)" = "1" ] && [ "$(cfg_get enabled 1)" = "1" ]; then
	( boot_dial ) >/dev/null 2>&1 &
fi
( sleep 20; sim_boot_init; sleep 40; sim_boot_init; sleep 60; sim_boot_init ) >/dev/null 2>&1 &
( sleep 25; fcc_boot_unlock ) >/dev/null 2>&1 &

# ---- 网卡模式切换的回头核对
modesw_check() { # <modem_id>
	local id="$1" f want att old ts stage km now retry=0
	f="$CELL_RUN/modesw_$id"
	[ -s "$f" ] || return 0
	read -r want att old ts stage < "$f" 2>/dev/null
	is_num "$att" || att=1
	is_num "$ts" || ts=0
	now=$(date +%s)
	if [ $(( now - ts )) -gt 600 ]; then rm -f "$f" 2>/dev/null; return 0; fi
	case "$stage" in
		gone) ;;
		stay) retry=1 ;;
		*)    [ $(( now - ts )) -lt 90 ] && return 0 ;;   # modereboot 还没报结果
	esac
	km=$(modem_field "$id" mode 2>/dev/null)
	if [ "$retry" = "0" ]; then
		if [ -n "$want" ] && [ "$km" = "$want" ]; then
			rm -f "$f" 2>/dev/null
			cell_event cfg "网卡模式已切换为 $want"
			return 0
		fi
		if [ -z "$km" ]; then
			[ $(( now - ts )) -lt 240 ] && return 0
			rm -f "$f" 2>/dev/null
			cell_event down "网卡模式已设为 $want, 但路由器上还没有认出对应的网卡驱动(缺内核模块时需要先安装, 例如 MBIM 要 kmod-usb-net-cdc-mbim)"
			return 1
		fi
		[ "$km" = "$old" ] && retry=1
	fi
	if [ "$retry" = "1" ] && [ "$att" -lt 2 ]; then
		printf '%s 2 %s %s\n' "$want" "$old" "$now" > "$f" 2>/dev/null
		cell_event cfg "网卡模式第一次没有生效(模组$([ "$stage" = "stay" ] && echo "没有重启" || echo "重启回来还是 $km")), 自动再切换一次 $want"
		netchg_set "$id" 90 mode "正在按新的网卡模式重启模组并重新枚举"
		printf '%s %s 0\n' "$(( now + 45 ))" "$id" > "$CELL_RUN/reboot_wait"
		if command -v setsid >/dev/null 2>&1; then
			setsid "$CELL_CLI" modereboot "$id" "" "$want" 2 >/dev/null 2>&1 </dev/null &
		else
			( "$CELL_CLI" modereboot "$id" "" "$want" 2 >/dev/null 2>&1 </dev/null & ) &
		fi
		return 0
	fi
	rm -f "$f" 2>/dev/null
	cell_event down "网卡模式要切到 $want, 模组现在是 ${km:-未知} —— 可以在「设备信息 -> 网卡模式」里再切一次"
	return 1
}
modesw_check_all() {
	local f
	for f in "$CELL_RUN"/modesw_*; do
		[ -s "$f" ] || continue
		modesw_check "${f##*/modesw_}"
	done
	return 0
}

# ---- 频段的开机恢复
# 用户点「应用」时那份频段选择同时存进了 /etc/config/cellular(conffile,
# 升级不丢)。开机后对一次: 一致就什么都不做(一条写指令都不发, 不碰 NV),
# 只有模组那边真的被冲掉了(固件恢复出厂 / 换了模组 / 少数固件在 CFUN=1,1
# 之后退回默认)才补写一次。
band_sync_spawn() { # <modem_id>
	[ -n "$1" ] || return 1
	if command -v setsid >/dev/null 2>&1; then
		setsid "$CELL_CLI" bandsync "$1" >/dev/null 2>&1 </dev/null &
	else
		( "$CELL_CLI" bandsync "$1" >/dev/null 2>&1 </dev/null & ) &
	fi
	return 0
}

# ---- 短信同步
sms_sync_spawn() { # <modem_id>
	[ -n "$1" ] || return 1
	if command -v setsid >/dev/null 2>&1; then
		setsid "$CELL_CLI" smssync "$1" >/dev/null 2>&1 </dev/null &
	else
		( "$CELL_CLI" smssync "$1" >/dev/null 2>&1 </dev/null & ) &
	fi
	return 0
}

# ---- 「读取 QCI」没来得及切回的优先级
# 界面 QCI 那一格的「读取」按钮会把驻网优先级临时切到「仅 4G」再切回(cellular qciprobe)。
# 动手之前它把"原来是什么"记在了 flash 上(uci 的 qosmem_<id>.restore), 这里是兜底:
# * 开机后对一次: 记号还在 = 上次没来得及切回(路由器掉电 / 进程被杀), 补一次;
# * 运行中: 记号文件的主人(那个进程)没了, 也补一次。
# `cellular qcirestore` 自己会先读一条比对, 模组里已经是那个值就只撤记号, 一条写指令都不发;
# 两次之间至少隔 120 秒, 免得模组不接受时没完没了。没有记号时这里只是看一眼文件在不在。
qci_restore_spawn() { # <modem_id>
	[ -n "$1" ] || return 1
	cool_ok "qcirestore_$1" 120 || return 0
	cool_mark "qcirestore_$1"
	if command -v setsid >/dev/null 2>&1; then
		setsid "$CELL_CLI" qcirestore "$1" >/dev/null 2>&1 </dev/null &
	else
		( "$CELL_CLI" qcirestore "$1" >/dev/null 2>&1 </dev/null & ) &
	fi
	return 0
}

# ---- 开机在日志里交代一句后台都开了什么
boot_summary() {
	local d="" a b c
	command -v dial_enabled_count >/dev/null 2>&1 && d=$(dial_enabled_count 2>/dev/null)
	is_num "$d" || d=0
	a="关"; b="关"
	command -v wd_load >/dev/null 2>&1 && { wd_load; [ "$WD_EN" = "1" ] && a="开"; }
	command -v no_load >/dev/null 2>&1 && { no_load; [ "$NO_EN" = "1" ] && b="开"; }
	if [ "$(cfg_get auto_dial 1)" = "1" ]; then c="开（${d} 条拨号）"; else c="关"; fi
	cell_event sys "后台服务已启动：自动拨号 $c · 断线重拨 $([ "$(cfg_get link_guard 1)" != "0" ] && echo 开 || echo 关) · 网络看门狗 $a · 网络优选 $b · 始终监听 $([ "$(cfg_get always_monitor 0)" = "1" ] && echo 开 || echo 关) · 短信 $([ "$(cfg_get sms_enabled 1)" != "0" ] && echo 开 || echo 关)（不打开界面也照常工作）"
	return 0
}
[ "$(cfg_get enabled 1)" = "1" ] && boot_summary

CELL_START=$(date +%s)
CYCLE=0
FULLC=0
BANDBOOT=0
QCIBOOT=0
HISTGC=0
BGLAST=0
SMSLAST=0
SMSIDLE_N=0
SMSARM_TS=0
SMSARM_N=0
LAST=0
LAST_SCAN=$(date +%s)
CFG_AT=0
HBCHK=0
NOWORKER=0
LINKCHK=0
REJCHK=0
LINKBAD=0
LINKFIXN=0
LINKWAIT=150
WDCHK=0
NOCHK=0
PREFCHK=0
ATCHK=0
ATGONE=0
ATWAIT=15

# 配置每 10 秒重读一次就够了 —— 每秒 fork 一次 uci 在弱路由上很伤
reload_cfg() {
	ENABLED=$(cfg_get enabled 1)
	INTERVAL=$(cfg_get poll_interval 8)
	is_num "$INTERVAL" || INTERVAL=8
	[ "$INTERVAL" -lt 2 ] && INTERVAL=2
	NET_INTERVAL=$(cfg_get net_interval 5)
	is_num "$NET_INTERVAL" || NET_INTERVAL=5
	[ "$NET_INTERVAL" -lt 2 ] && NET_INTERVAL=2
	MONITOR=$(cfg_get always_monitor 0)
	MONINT=$(cfg_get monitor_interval 60)
	is_num "$MONINT" || MONINT=60
	[ "$MONINT" -lt 20 ] && MONINT=20
	WTTL=$(cfg_get watch_ttl 20)
	is_num "$WTTL" || WTTL=20
	[ "$WTTL" -lt 10 ] && WTTL=10
	IDLE_SCAN=$(cfg_get idle_rescan 600)
	is_num "$IDLE_SCAN" || IDLE_SCAN=600
	LINKGUARD=$(cfg_get link_guard 1)
	SMSON=$(cfg_get sms_enabled 1)
	SMSPOLL=$(cfg_get sms_poll 300)
	is_num "$SMSPOLL" || SMSPOLL=300
	[ "$SMSPOLL" -lt 30 ] && SMSPOLL=30
	SMSFAST=$(cfg_get sms_fast 15)
	is_num "$SMSFAST" || SMSFAST=15
	[ "$SMSFAST" -lt 5 ] && SMSFAST=5
	SMSPOLLMAX=$(cfg_get sms_poll_max 900)
	is_num "$SMSPOLLMAX" || SMSPOLLMAX=900
	[ "$SMSPOLLMAX" -lt "$SMSPOLL" ] && SMSPOLLMAX="$SMSPOLL"
	PREFGUARD=$(cfg_get pref_guard 1)
	PREFINT=$(cfg_get pref_guard_interval 900)
	is_num "$PREFINT" || PREFINT=900
	[ "$PREFINT" -lt 120 ] && PREFINT=120
}
reload_cfg

while :; do
	NOW=$(date +%s)
	if [ $(( NOW - CFG_AT )) -ge 10 ]; then reload_cfg; CFG_AT="$NOW"; fi

	if [ "$ENABLED" = "0" ]; then
		rm -f "$CELL_RUN"/info_*.json 2>/dev/null
		sleep 5
		continue
	fi

	# ---- AT 队列 supervisor 看护
	if [ $(( NOW - HBCHK )) -ge 20 ]; then
		HBCHK="$NOW"
		if atq_sup_alive; then
			NOWORKER=0
		else
			NOWORKER=$(( NOWORKER + 1 ))
			if [ "$NOWORKER" -ge 2 ]; then
				NOWORKER=0
				cell_log "AT 队列 supervisor 不在, 自己拉一个"
				if command -v setsid >/dev/null 2>&1; then
					setsid /usr/sbin/cellular-atq >/dev/null 2>&1 </dev/null &
				else
					( /usr/sbin/cellular-atq >/dev/null 2>&1 </dev/null & ) &
				fi
				sleep 2
			fi
		fi
	fi

	# ---- 链路看护: 移动网络起来了却拿不到地址就把接口重拉一遍
	if [ -s "$CELL_RUN/redial_now" ] && ! at_busy; then
		RD_AT=""; RD_ID=""
		read -r RD_AT RD_ID < "$CELL_RUN/redial_now" 2>/dev/null
		is_num "$RD_AT" || RD_AT=0
		if [ "$NOW" -ge "$RD_AT" ]; then
			rm -f "$CELL_RUN/redial_now"
			if [ -n "$RD_ID" ] && [ -s "$CELL_RUN/info_$RD_ID.json" ]; then
				RD_FLY="$CELL_RUN/info_$RD_ID.json"
			else
				RD_FLY="$CELL_RUN"/info_*.json
			fi
			if grep -q '"flight":1' $RD_FLY 2>/dev/null; then
				cell_log "飞行模式中, 跳过这次重拨"
			elif command -v dial_redial >/dev/null 2>&1; then
				cell_event cfg "网络已切换, 正在重新建立数据连接"
				command -v dial_ensure_all >/dev/null 2>&1 && dial_ensure_all >/dev/null 2>&1
				dial_redial "$RD_ID" >/dev/null 2>&1
			fi
			LINKBAD=0
			cool_mark linkfix
		fi
	fi
	if [ "$LINKGUARD" != "0" ] && [ $(( NOW - LINKCHK )) -ge 20 ] && \
	   [ ! -s "$CELL_RUN/redial_now" ] && ! at_busy; then
		LINKCHK="$NOW"
		if grep -q '"flight":1' "$CELL_RUN"/info_*.json 2>/dev/null; then
			LINKBAD=0
		# ---- 接口自愈
		elif command -v dial_ensure_all >/dev/null 2>&1 && cool_ok dialfix 30 && \
		     dial_ensure_all >/dev/null 2>&1; then
			cool_mark dialfix
			LINKBAD=0
		elif command -v dial_link_bad >/dev/null 2>&1 && [ -n "$(dial_link_bad)" ]; then
			LINKBAD=$(( LINKBAD + 1 ))
			LINKWAIT=150
			[ "$LINKFIXN" -ge 3 ] && LINKWAIT=300
			[ "$LINKFIXN" -ge 6 ] && LINKWAIT=600
			if [ "$LINKBAD" -ge 2 ] && cool_ok linkfix "$LINKWAIT"; then
				LINKBAD=0
				LINKFIXN=$(( LINKFIXN + 1 ))
				cool_mark linkfix
				cell_event down "移动网络已注册但拿不到地址(或停在 0.0.0.0), 自动重新拨号一次(第 $LINKFIXN 次)"
				dial_redial "" >/dev/null 2>&1
			fi
		else
			LINKBAD=0
			LINKFIXN=0
		fi
	fi

	# ---- 被运营商拒绝的拨号: 按退避重拨, 直到拿到地址
	if [ "$LINKGUARD" != "0" ] && [ $(( NOW - REJCHK )) -ge 20 ] && \
	   [ ! -s "$CELL_RUN/redial_now" ] && ! at_busy && command -v dial_reject_step >/dev/null 2>&1; then
		REJCHK="$NOW"
		dial_reject_step >/dev/null 2>&1
	fi
	if [ "$LINKGUARD" = "0" ]; then
		for _hf in "$CELL_RUN"/dialhold_*; do
			[ -e "$_hf" ] || continue
			rm -f "$_hf" 2>/dev/null
			ifup "${_hf##*/dialhold_}" >/dev/null 2>&1
		done
	fi
	for _sf in "$CELL_RUN"/siminit_*; do
		[ -e "$_sf" ] || continue
		case "${_sf##*/}" in siminit_n_*) continue ;; esac
		_sid=${_sf##*/siminit_}
		rm -f "$_sf" 2>/dev/null
		[ -n "$_sid" ] || continue
		if command -v setsid >/dev/null 2>&1; then
			setsid "$CELL_CLI" siminit "$_sid" >/dev/null 2>&1 </dev/null &
		else
			( "$CELL_CLI" siminit "$_sid" >/dev/null 2>&1 </dev/null & ) &
		fi
	done
	if [ -e "$CELL_RUN/simurc" ]; then
		rm -f "$CELL_RUN/simurc" 2>/dev/null
		for _sid in $(cellular_modem_ids 2>/dev/null); do
			if command -v setsid >/dev/null 2>&1; then
				setsid "$CELL_CLI" siminit "$_sid" >/dev/null 2>&1 </dev/null &
			else
				( "$CELL_CLI" siminit "$_sid" >/dev/null 2>&1 </dev/null & ) &
			fi
		done
	fi

	# ---- 网络看门狗
	if [ $(( NOW - WDCHK )) -ge 10 ] && command -v wd_step >/dev/null 2>&1; then
		WDCHK="$NOW"
		wd_step >/dev/null 2>&1
	fi

	# ---- 网络优选
	if [ $(( NOW - NOCHK )) -ge 15 ] && command -v no_step >/dev/null 2>&1; then
		NOCHK="$NOW"
		no_step >/dev/null 2>&1
	fi

	# ---- 信号历史的兜底清理
	if [ $(( NOW - HISTGC )) -ge 600 ] && command -v hist_gc_all >/dev/null 2>&1; then
		HISTGC="$NOW"
		hist_gc_all >/dev/null 2>&1
	fi

	if at_busy; then
		cell_log "串口忙($(at_busy_what)), 本轮跳过采集"
		sleep 2
		continue
	fi

	if [ -f "$RESCAN_FLAG" ]; then
		rm -f "$RESCAN_FLAG"
		at_prio 7
		cellular_scan >/dev/null 2>&1
		LAST_SCAN=$(date +%s)
		cell_log "按请求重新扫描完成"
		modesw_check_all
	fi

	# ---- AT 全挂自愈
	if [ $(( NOW - ATCHK )) -ge "$ATWAIT" ] && command -v cellular_has_at_port >/dev/null 2>&1; then
		ATCHK="$NOW"
		if [ "$(cellular_modem_count 2>/dev/null || echo 0)" -gt 0 ] 2>/dev/null &&
		   ! cellular_has_at_port; then
			ATGONE=$(( ATGONE + 1 ))
			[ "$ATGONE" = "1" ] && \
				cell_event down "认到模组但没有可用的 AT 端口, 正在重新探测串口"
			at_prio 7
			cellular_scan >/dev/null 2>&1
			LAST_SCAN=$(date +%s)
			if cellular_has_at_port; then
				cell_event sys "AT 端口已恢复"
				ATGONE=0; ATWAIT=15
			else
				ATWAIT=$(( ATWAIT * 2 ))
				[ "$ATWAIT" -gt 300 ] && ATWAIT=300
				if [ "$ATGONE" = "5" ]; then
					cell_event down "串口连续探不通, 尝试重新枚举模组的 USB(不影响其它设备)"
					cellular_usb_reset "" soft >/dev/null 2>&1
					: > "$RESCAN_FLAG" 2>/dev/null
				fi
			fi
		else
			ATGONE=0; ATWAIT=15
		fi
	fi

	if [ -s "$CELL_RUN/reboot_wait" ]; then
		RW_UNTIL=$(cut -d' ' -f1 "$CELL_RUN/reboot_wait" 2>/dev/null)
		RW_ID=$(cut -d' ' -f2 "$CELL_RUN/reboot_wait" 2>/dev/null)
		RW_TRIED=$(cut -d' ' -f3 "$CELL_RUN/reboot_wait" 2>/dev/null)
		is_num "$RW_UNTIL" || RW_UNTIL=0
		if [ "$(date +%s)" -ge "$RW_UNTIL" ]; then
			at_prio 7
			cellular_scan >/dev/null 2>&1
			LAST_SCAN=$(date +%s)
			if [ -n "$(modem_field "" at_port 2>/dev/null)" ]; then
				cell_event sys "模组重启后已恢复"
				modesw_check_all
				rm -rf "$CELL_RUN"/simboot_* 2>/dev/null
				( sleep 8; sim_boot_init ) >/dev/null 2>&1 &
				if command -v sms_push_rearm >/dev/null 2>&1; then
					sms_push_rearm
					[ "$SMSON" != "0" ] && : > "$CELL_RUN/sms_now" 2>/dev/null
				fi
				rm -f "$CELL_RUN/reboot_wait"
			elif [ "$RW_TRIED" != "1" ]; then
				cell_event sys "模组重启后没有回来, 正在尝试重新枚举 USB"
				cellular_usb_reset "$RW_ID" soft >/dev/null 2>&1
				printf '%s %s 1\n' "$(( $(date +%s) + 25 ))" "$RW_ID" > "$CELL_RUN/reboot_wait"
			else
				cell_event sys "模组重启后仍未恢复。到「设备信息 → 快捷控制」用「一键恢复模组」做一次 USB 总线复位, 不用重启整机"
				rm -f "$CELL_RUN/reboot_wait"
			fi
		fi
	fi

	# ---- 频段: 开机对一次, 之后靠深读轮发现漂了再补
	if [ "$BANDBOOT" = "0" ] && [ "$(date +%s)" -ge $(( CELL_START + 25 )) ]; then
		BANDBOOT=1
		for _bid in $(cellular_modem_ids 2>/dev/null); do
			band_sync_spawn "$_bid"
		done
	fi
	if [ "$QCIBOOT" = "0" ] && [ "$(date +%s)" -ge $(( CELL_START + 35 )) ]; then
		QCIBOOT=1
		if command -v qos_mem_load >/dev/null 2>&1; then
			for _qid in $(cellular_modem_ids 2>/dev/null); do
				qos_mem_load "$_qid" >/dev/null 2>&1 || continue
				[ -n "$QM_RESTORE" ] && qci_restore_spawn "$_qid"
			done
		fi
	fi
	for _qf in "$CELL_RUN"/qciprobe_*; do
		[ -s "$_qf" ] || continue
		case "${_qf##*/}" in qciprobe_res_*) continue ;; esac
		_qt=""; _qp=""; read -r _qt _qp _qk _qs < "$_qf" 2>/dev/null
		if is_num "$_qp" && kill -0 "$_qp" 2>/dev/null; then continue; fi
		qci_restore_spawn "${_qf##*/qciprobe_}"
	done

	for _bf in "$CELL_RUN"/bandfix_*; do
		[ -e "$_bf" ] || continue
		_bid=${_bf##*/bandfix_}
		rm -f "$_bf" 2>/dev/null
		[ -n "$_bid" ] && band_sync_spawn "$_bid"
	done

	# ---- 驻网优先级: 模组把它改回去了就补回来
	for _pf in "$CELL_RUN"/preffix_*; do
		[ -e "$_pf" ] || continue
		_pid2=${_pf##*/preffix_}
		rm -f "$_pf" 2>/dev/null
		[ -n "$_pid2" ] || continue
		if command -v setsid >/dev/null 2>&1; then
			setsid "$CELL_CLI" prefsync "$_pid2" >/dev/null 2>&1 </dev/null &
		else
			( "$CELL_CLI" prefsync "$_pid2" >/dev/null 2>&1 </dev/null & ) &
		fi
	done

	# ---- 驻网优先级的"定期守护"
	if [ "$PREFGUARD" != "0" ] && [ $(( NOW - PREFCHK )) -ge "$PREFINT" ] && \
	   [ "$NOW" -ge $(( CELL_START + 90 )) ]; then
		PREFCHK="$NOW"
		if ! at_busy; then
			for _pid2 in $(cellular_modem_ids 2>/dev/null); do
				pref_want_load "$_pid2" >/dev/null 2>&1 || continue
				[ "$PREFWANT_EN" = "1" ] || continue
				case "$PREFWANT" in auto|'') continue ;; esac
				if command -v setsid >/dev/null 2>&1; then
					setsid "$CELL_CLI" prefsync "$_pid2" >/dev/null 2>&1 </dev/null &
				else
					( "$CELL_CLI" prefsync "$_pid2" >/dev/null 2>&1 </dev/null & ) &
				fi
			done
		fi
	fi

	# ---- 短信: 模组报了新短信就立刻收, 否则按节奏兜底
	# ---- 主动接收的武装看护
	if [ "$SMSON" != "0" ] && command -v sms_push_armed_any >/dev/null 2>&1; then
		if sms_push_armed_any; then
			SMSARM_N=0
		else
			SMSARM_W=$(( 60 << (SMSARM_N > 3 ? 3 : SMSARM_N) ))
			[ "$SMSARM_W" -gt 600 ] && SMSARM_W=600
			if [ $(( NOW - SMSARM_TS )) -ge "$SMSARM_W" ] && \
			   [ "$(date +%s)" -ge $(( CELL_START + 25 )) ] && ! at_busy; then
				SMSARM_TS="$NOW"
				SMSARM_N=$(( SMSARM_N + 1 ))
				for _sid in $(cellular_modem_ids 2>/dev/null); do
					if command -v setsid >/dev/null 2>&1; then
						setsid "$CELL_CLI" smsarm "$_sid" >/dev/null 2>&1 </dev/null &
					else
						( "$CELL_CLI" smsarm "$_sid" >/dev/null 2>&1 </dev/null & ) &
					fi
				done
			fi
		fi
	fi

	if [ "$SMSON" != "0" ]; then
		SMSDO=0; SMSBG=0
		SMSFAST_E="$SMSFAST"; SMSPOLL_E="$SMSPOLL"
		if command -v sms_push_armed_any >/dev/null 2>&1 && sms_push_armed_any; then
			[ "$SMSFAST_E" -lt 45 ] && SMSFAST_E=45
			SMSPOLL_E=$(( SMSPOLL * 3 ))
			[ "$SMSPOLL_E" -lt 600 ] && SMSPOLL_E=600
			[ "$SMSPOLL_E" -gt 1800 ] && SMSPOLL_E=1800
		else
			SMSPOLL_E=$(( SMSPOLL << (SMSIDLE_N > 4 ? 4 : SMSIDLE_N) ))
			[ "$SMSPOLL_E" -gt "$SMSPOLLMAX" ] && SMSPOLL_E="$SMSPOLLMAX"
			[ "$SMSPOLL_E" -lt "$SMSPOLL" ] && SMSPOLL_E="$SMSPOLL"
		fi
		if [ -f "$CELL_RUN/sms_now" ]; then
			rm -f "$CELL_RUN/sms_now" 2>/dev/null
			SMSDO=1; SMSIDLE_N=0
		elif watch_active "$WTTL" sms; then
			SMSIDLE_N=0
			[ $(( NOW - SMSLAST )) -ge "$SMSFAST_E" ] && SMSDO=1
		elif [ $(( NOW - SMSLAST )) -ge "$SMSPOLL_E" ] && \
		     [ "$(date +%s)" -ge $(( CELL_START + 20 )) ]; then
			SMSDO=1; SMSBG=1
		fi
		if [ "$SMSDO" = "1" ]; then
			if [ "$SMSBG" = "1" ]; then
				SMSLASTN=$(cat "$CELL_RUN/sms_lastn" 2>/dev/null)
				is_num "$SMSLASTN" || SMSLASTN=0
				if [ "$SMSLASTN" -gt 0 ]; then SMSIDLE_N=0; else SMSIDLE_N=$(( SMSIDLE_N + 1 )); fi
			fi
			SMSLAST="$NOW"
			for _sid in $(cellular_modem_ids 2>/dev/null); do
				sms_sync_spawn "$_sid"
			done
		fi
	fi

	# ---- 谁在看界面
	WATCHING=0
	COLDFILL=0
	watch_active "$WTTL" overview network && WATCHING=1
	if [ "$WATCHING" = "0" ] && command -v coldfill_due >/dev/null 2>&1 && \
	   coldfill_due "$WTTL"; then
		WATCHING=1; COLDFILL=1
	fi
	EVERY="$INTERVAL"
	if watch_active "$WTTL" network && [ "$NET_INTERVAL" -lt "$INTERVAL" ]; then
		EVERY="$NET_INTERVAL"
	fi
	if [ "$WATCHING" = "1" ] && [ "$COLDFILL" = "0" ] && \
	   command -v watch_idle_all >/dev/null 2>&1 && \
	   watch_idle_all "$WTTL" overview network; then
		EVERY=$(( EVERY * 3 ))
		[ "$EVERY" -gt 30 ] && EVERY=30
	fi

	# ---- 后台补采: 网络优选要一份新的快照
	BGDO=0
	if [ -f "$CELL_RUN/bg_collect" ]; then
		if [ "$WATCHING" = "1" ] || watch_active "$WTTL" at; then
			rm -f "$CELL_RUN/bg_collect" 2>/dev/null
		elif [ $(( NOW - BGLAST )) -ge 60 ]; then
			rm -f "$CELL_RUN/bg_collect" 2>/dev/null
			BGDO=1
		fi
	fi

	NOW=$(date +%s)
	DO=0; LIGHT=0
	if [ -f "$NOW_FLAG" ]; then
		rm -f "$NOW_FLAG"
		if [ "$WATCHING" = "1" ] || [ "$MONITOR" = "1" ]; then
			DO=1
			if [ $(( NOW - LAST_SCAN )) -ge "$IDLE_SCAN" ]; then
				at_prio 7
				cellular_scan >/dev/null 2>&1
				LAST_SCAN=$(date +%s)
			fi
		fi
	fi
	if [ "$DO" = "0" ]; then
		if [ "$WATCHING" = "1" ]; then
			[ $(( NOW - LAST )) -ge "$EVERY" ] && DO=1
		elif [ "$MONITOR" = "1" ]; then
			[ $(( NOW - LAST )) -ge "$MONINT" ] && { DO=1; LIGHT=1; }
		fi
	fi

	if [ "$BGDO" = "1" ]; then DO=1; LIGHT=0; fi

	if [ "$DO" = "1" ]; then
		LAST="$NOW"
		if [ "$LIGHT" = "1" ]; then
			at_prio 7 8000
			cellular_sample_all >/dev/null 2>&1
		else
			FULLC=$((FULLC+1))
			if [ "$COLDFILL" = "1" ] || [ "$BGDO" = "1" ] || [ $FULLC -ge 5 ]; then CELL_FULL=1; FULLC=0; else CELL_FULL=0; fi
			CELL_BG=0; CELL_NOHIST=0
			if [ "$BGDO" = "1" ] && [ "$WATCHING" != "1" ]; then
				CELL_BG=1
				[ "$MONITOR" = "1" ] || CELL_NOHIST=1
				BGLAST="$NOW"
			fi
			export CELL_FULL CELL_BG CELL_NOHIST
			at_prio 5 6000
			cellular_collect_all >/dev/null 2>&1
			CELL_BG=0; CELL_NOHIST=0
			export CELL_BG CELL_NOHIST
			collect_stamp
			CYCLE=$((CYCLE+1))
			if [ $CYCLE -ge 50 ]; then
				CYCLE=0
				at_prio 7
				cellular_scan >/dev/null 2>&1
				LAST_SCAN=$(date +%s)
			fi
		fi
	fi

	if [ "$WATCHING" = "1" ] || [ -s "$CELL_RUN/redial_now" ]; then
		sleep 2
	else
		[ "$MONITOR" = "1" ] && SLMAX=4 || SLMAX=6
		SL=0
		while [ "$SL" -lt "$SLMAX" ]; do
			sleep 2
			SL=$(( SL + 2 ))
			[ -f "$NOW_FLAG" ] && break
			[ "$SMSON" != "0" ] && [ -f "$CELL_RUN/sms_now" ] && break
			watch_active "$WTTL" overview network && break
		done
	fi
done
