Radxa 开发板的常驻 WiFi 配网服务。开机自动检测网络,无 WiFi 时开热点供用户配网,有 WiFi 时提供管理页面。
- Debian 11+ (aarch64)
- Python 3.9+ (仅用 stdlib,无需 pip 安装任何包)
- NetworkManager (nmcli 1.30+)
- nginx (已安装且
sites-available/default可写) - avahi-daemon (可选,提供 mDNS/.local 域名发现)
源文件 → 板卡目标路径
─────────────────────────────────────────────────────────────
lobster.py → /opt/lobster/lobster.py
index.html → /var/www/html/index.html ← nginx web root,重要!
lobster.service → /etc/systemd/system/lobster.service
lobster-web.service → /etc/avahi/services/lobster-web.service
nginx-default → /etc/nginx/sites-available/default
captive-portal.conf → /etc/NetworkManager/dnsmasq-shared.d/captive-portal.conf
注意:index.html 由 nginx 直接提供,必须放到 /var/www/html/,不是 /opt/lobster/。
index.html 中有一处需要根据实际板卡 hostname 修改:
// index.html 第 365 行附近
const PROBE_HOSTS = ["clawbox.local", "clawbox", "radxa-cubie-a7a.local", "radxa-cubie-a7a"];改成实际的板卡 hostname。此值用于 AP→STA 切换后,前端自动探测板卡新地址。
lobster.py 顶部常量按需修改:
| 常量 | 默认值 | 说明 |
|---|---|---|
WIFI_IFACE |
wlan0 |
WiFi 网卡名 |
AP_PASSWORD |
88888888 |
热点密码 |
WIFI_CHECK_TIMEOUT |
60 |
开机等待 WiFi 的秒数 |
API_PORT |
8899 |
HTTP API 端口(同时需改 nginx-default 里的 proxy_pass) |
OPENCLAW_PORT |
18790 |
OpenClaw 控制台端口(前端跳转用) |
以下所有命令在板卡上以 root 执行。假设源文件已上传到板卡的 /tmp/lobster/。
python3 --version # 需要 3.9+
nmcli --version # 需要 NetworkManager
nginx -v # 需要 nginx
avahi-daemon --check # 可选,返回 0 表示正常mkdir -p /opt/lobster
cp /tmp/lobster/lobster.py /opt/lobster/lobster.py
chmod +x /opt/lobster/lobster.pycp /tmp/lobster/index.html /var/www/html/index.html为什么是 /var/www/html/? 因为 nginx 配置中 root /var/www/html;,浏览器访问 http://板卡IP/ 时 nginx 直接从这里读文件。lobster.py 本身不提供静态文件,它只提供 /api/* 接口。
# 备份原配置
cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
# 部署新配置
cp /tmp/lobster/nginx-default /etc/nginx/sites-available/default
# 验证语法
nginx -t
# 重载(不要用 restart,reload 不断开现有连接)
systemctl reload nginxnginx-default 做了两件事:
location /api/→ 反向代理到127.0.0.1:8899(lobster.py)location /→try_files $uri $uri/ /index.html(任意路径 fallback 到配网页面,captive portal 需要此行为)
# 确认目录存在
ls -d /etc/NetworkManager/dnsmasq-shared.d/
# 部署
cp /tmp/lobster/captive-portal.conf /etc/NetworkManager/dnsmasq-shared.d/作用:AP 热点模式下,dnsmasq 将所有 DNS 查询解析到 10.42.0.1(网关),手机连上热点后自动弹出"登录此网络"页面。此配置仅在 NetworkManager shared 模式(即 AP 热点)下生效,不影响正常 WiFi。
如果目录不存在,跳过此步。影响:手机连上热点后不会自动弹出页面,需要用户手动打开浏览器访问 10.42.0.1。
cp /tmp/lobster/lobster-web.service /etc/avahi/services/
systemctl restart avahi-daemon作用:在局域网广播 _http._tcp 服务,名称"大龙虾控制台"。Windows/Mac/iOS 可通过 http://<hostname>.local/ 直接访问。Android 用户可安装 BonjourBrowser (wellenvogel, Google Play, 开源免费) 扫描发现。
cp /tmp/lobster/lobster.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable lobster.service
systemctl start lobster.service# 1. 服务是否在运行
systemctl status lobster.service
# 预期:active (running)
# 2. API 直连(lobster.py 本体)
curl http://127.0.0.1:8899/api/status
# 预期:{"mode":"sta","ssid":"...","ip":"...","hostname":"...","ap_ssid":"大龙虾_xxxx",...}
# 3. API 经过 nginx 代理
curl http://127.0.0.1/api/status
# 预期:同上
# 4. 前端页面
curl -s http://127.0.0.1/ | head -3
# 预期:<!DOCTYPE html> ... 大龙虾控制台
# 5. 实时日志
journalctl -u lobster.service -f如果板卡已经部署过,需要更新文件时:
# 1. 停服务
systemctl stop lobster.service
# 2. 覆盖文件(按需)
cp /tmp/lobster/lobster.py /opt/lobster/lobster.py
cp /tmp/lobster/index.html /var/www/html/index.html
# 如果改了 nginx 配置:
cp /tmp/lobster/nginx-default /etc/nginx/sites-available/default
nginx -t && systemctl reload nginx
# 3. 重启
systemctl start lobster.service开机
│
├─ Phase 0: 清理残留
│ ├─ 检测 8899 端口是否被占用,是则 kill
│ ├─ 删除残留的 lobster-ap 连接(上次崩溃留下的)
│ └─ 写 PID 文件到 /tmp/lobster.pid
│
├─ Phase 1: 检测 WiFi(每 5 秒检查一次,共 60 秒)
│ ├─ 已连接 → 进入 STA 模式,记录当前连接名
│ └─ 60 秒仍未连接 → Phase 2
│
├─ Phase 2: 开启 AP 热点
│ ├─ nmcli device wifi hotspot ...
│ ├─ 禁用 IPv6(加速客户端 DHCP)
│ ├─ SSID: 大龙虾_<machine-id末4位>
│ ├─ 密码: 88888888
│ └─ 网关: 10.42.0.1
│
└─ Phase 3: HTTP API 常驻运行 (127.0.0.1:8899)
无论 AP 还是 STA 模式都运行
用户通过 nginx :80 代理访问
所有接口通过 nginx http://板卡地址/api/ 访问。
| 方法 | 路径 | 请求体 | 说明 |
|---|---|---|---|
| GET | /api/status |
- | 当前模式、SSID、IP、watchdog 剩余秒数 |
| GET | /api/scan |
- | 扫描附近 WiFi 列表(AP 模式下可能为空) |
| POST | /api/connect |
{"ssid":"名称","password":"密码"} |
连接指定 WiFi,失败自动回滚 |
| POST | /api/mode |
{"mode":"ap","timeout":300} |
手动切 AP,必须带 timeout(秒),超时自动恢复 |
| POST | /api/mode |
{"mode":"sta"} |
手动切回之前的 WiFi |
核心原则:任何网络操作失败都不能让板卡失联。
POST /api/connect失败 → 回滚到之前的连接(AP 模式下恢复 AP,STA 模式下恢复旧 WiFi)POST /api/mode切 AP → 强制带 watchdog 定时器,超时无人访问则自动恢复 WiFi- AP 启动失败 → 尝试连回之前的 WiFi
- WiFi 恢复失败 → 重新开 AP
- 以上全部失败 → 遍历板卡所有已保存的 WiFi 连接逐个尝试,最后兜底开 AP
| 场景 | 访问地址 |
|---|---|
| STA 模式(已联网) | http://<hostname>.local/ 或 http://<IP>/ |
| AP 模式(配网热点) | 手机连热点后自动弹出页面;或手动访问 http://10.42.0.1/ |
手机/电脑浏览器
│
│ http://<板卡>:80
▼
nginx (:80)
│
├── GET / → /var/www/html/index.html (配网页面)
├── GET /api/* → proxy_pass 127.0.0.1:8899 (lobster.py)
└── POST /api/* → proxy_pass 127.0.0.1:8899 (lobster.py)
lobster.py (127.0.0.1:8899)
│
├── HTTP API 处理
├── nmcli 控制 WiFi/AP
└── watchdog 线程(AP 超时自动恢复)
avahi-daemon
└── 广播 _http._tcp "大龙虾控制台"
dnsmasq (NetworkManager shared mode)
└── AP 模式下:所有 DNS → 10.42.0.1 (captive portal)
systemctl stop lobster.service
systemctl disable lobster.service
rm /etc/systemd/system/lobster.service
systemctl daemon-reload
rm -rf /opt/lobster
rm -f /var/www/html/index.html
rm -f /etc/NetworkManager/dnsmasq-shared.d/captive-portal.conf
rm -f /etc/avahi/services/lobster-web.service
# 恢复 nginx 备份
cp /etc/nginx/sites-available/default.bak /etc/nginx/sites-available/default
nginx -t && systemctl reload nginx# 服务状态和最近日志
systemctl status lobster.service
journalctl -u lobster.service --no-pager -n 50
# 端口是否被占用
ss -tlnp | grep 8899
# 杀残留进程
fuser -k 8899/tcp
# 手动启动看完整输出
systemctl stop lobster.service
python3 /opt/lobster/lobster.py
# 检查 nginx 配置语法
nginx -t
# 检查 AP 连接残留
nmcli connection show | grep lobster
# 清理:
nmcli connection delete lobster-ap
# 紧急恢复网络(接屏幕键盘操作)
nmcli connection delete lobster-ap
nmcli device wifi connect "WiFi名称" password "密码" ifname wlan0
systemctl restart lobster.service