Cellular Links¶
Detecting a 4G module, dialling and keeping up its PPP link, and recovering a
module that has stopped answering. Source: internal/wwan.
Detection and profiles¶
detect.go finds modules from usbdevs -v and the serial drivers OpenBSD
attaches (umsm, uark, uslcom, uftdi, umodem, ucom), and their
/dev/cuaU* callout devices. profiles.go maps USB ids to a profile with its
candidate AT ports and quirks:
| Profile | USB ids |
|---|---|
sierra-mdm9200 (MC7700 family) |
1199:68a3, 1199:68a2 |
huawei-generic |
12d1:1506, 12d1:1436, 12d1:14ac |
quectel-ec25 |
2c7c:0125, 2c7c:0121 |
generic-ppp |
anything else |
A device has at most one cellular interface (type wwan). The link is ppp0.
Every AT port access takes the UUCP lock /var/spool/lock/LCK..cuaUN, the same
one pppd uses, and takes over a stale lock left by a dead process.
edge-agent modem status and edge-agent modem at <cmd>... give a shell view of
the same module.
Applying the config¶
Apply writes /etc/digilan/wwan.chat (0600) and
/etc/ppp/pap-secrets.digilan, can read the APN from the module when the config
asks for automatic APN, and stops the link when settings change. It never dials
itself; the supervisor does.
pppd runs with 115200 noauth nodefaultroute noipdefault ipcp-accept-local
lcp-echo-interval 20 lcp-echo-failure 3, plus local and a peer address for
modules whose profile needs them. There is no persist, maxfail or
usepeerdns: the default route belongs to uplink failover,
and the agent owns the retry. pppd reopening the exclusive AT port on every
retry would interrupt a module that is still searching for a network.
The supervision loop¶
Supervise runs in its own goroutine, independent of any connection to the
platform, and calls superviseStep every 30 s (not in dry-run).
| State | What happens |
|---|---|
| Link up, probe verdict usable | Nothing. Reading the module would take the AT port from the running link |
Link up, verdict down for 90 s |
Rebuild the link, subject to the restart backoff |
pppd running, no address after 2 min |
Stop it |
| Link down, within 45 s of a stop | Wait (quiet period) |
| Link down | Read the module and dial if it has a network |
Restart backoff: 5 minutes, doubling to a 1 hour cap, forgotten after 30 minutes of healthy link.
An address is not reachability
linkUp only says ppp0 holds an address, meaning IPCP finished. Whether
anything crosses it is the failover probe's verdict, read through
wanfailover.ProbeVerdict. A link can hold an address, answer pppd's
echoes and carry nothing.
Dialling¶
dialWhenReady dials only when the module reports registration home or
roaming. Dialling a module that is still searching cannot succeed and costs it
the port it needs to register. Before dialling it programs the APN with
AT+CGDCONT when the module's APN differs (and dials on the next pass), and runs
ifconfig ppp0 create.
The pre-dial read is the only regular module reading: once the link is up,
pppd holds the AT port and status queries fail. The reading is cached in
/var/db/digilan/modems.json, keyed by module serial, and reported with the
current error attached, so the portal shows "this is what it last said" instead
of a blank card. The state-report path reads the module at most every 2 minutes
and never waits for a busy port.
Remedies¶
Each remedy has its own trigger and rate limit; they are not one sequence.
| Remedy | Commands | When | Limit |
|---|---|---|---|
| Data-mode escape | +++ then ATH, written blind with 1.2 s guards |
Dial path, no AT port answers, link not up | backoff 2 → 15 min |
| Operator re-select | AT+COPS=2, AT+COPS=0 |
SIM ready, registration denied | every 10 min |
| Radio cycle | AT+CFUN=0, 3 s, AT+CFUN=1 |
SIM ready, searching or not registered for 5 min | every 15 min |
| Reboot the device | reboot |
See below | once per 6 h |
Re-select wins over a radio cycle when both apply. Re-select and radio cycle are
decided by escalationFor, which both the dial path and the state-report path
use, so they share one rate limiter. A remedy that could not be delivered (no
port answered) does not consume its interval in the report path, and says so in
the log.
The escape log line carries opened=N total=N. Fewer opened than total means
something else holds the other ports; equal means the module is not speaking at
all, which is the case that needs power.
When nothing reaches the module¶
Every remedy except the escape is an AT command, so none reaches a module that
answers on no port. A device reboot does recover
such a module. lastresort.go makes that the last remedy, narrowly:
- only when the cellular link is the device's sole uplink (no other
interface has
WANPriority > 0in the config). With a wired uplink present it does nothing: rebooting would drop every client to rescue a backup nobody is using; - after 45 minutes with no port answering, at most once per 6 hours.
The pacing is on disk
The remedy restarts the process, so in-memory pacing would be erased by the
act it paces. The marker /var/db/digilan/wwan-reboot.json is written
(temporary file plus rename) before the reboot, and the reboot is
skipped if it cannot be written. A test discards all in-memory state as a
reboot would and asserts the remedy does not fire twice.
The self-heal ladder is separate: it acts when the whole device has no internet, whatever the uplink.