fix(vd960Air): 修复 initialize 组装崩溃(mobile.msisdn 不存在) + link 采集兜底

现象(第一批修复后): 模块成功注网(IP_READY 10.38.28.79)并连上 MQTT(conack nil nil /
suback true 0), 随即在组装 initialize 报文时崩溃:

  E/user.coroutine.resume link_info.lua:29: attempt to call a nil value (field 'msisdn')
    link_info.lua:29 in 'link_info.get'
    app_iot.lua:34   in upvalue 'build_initialize'
    app_iot.lua:65   in function 'app_iot.on_connected'
  E/main Lua VM exit!! reboot in 15000ms

根因: mobile.msisdn() 在 Air780EPM 上不存在。LuatOS 各固件 API 面不一致, 且字段不存在时
      "直接调用 = 抛异常崩 VM"(不是返回 nil) —— 与 mstick() 同性质。手机号官方 API 是
      mobile.number(simid)(见 demo/mobile/mobile_test.lua), 物联卡普遍也无机号。

修复(防御性, 两层):
1. link_info.lua: 新增 safe(fn, ...) 助手(type(fn)=="function" 判断 + pcall), 所有 mobile.*
   调用统一走它 → 字段缺失/调用异常一律返回 nil、不再抛出; msisdn 改用 safe(mobile.number, 0);
   csq 兼容 table 返回
2. app_iot.lua: 新增 get_link() 兜底(内部 pcall + 失败时返回全空 link 表), inject_link 与
   build_initialize 均改用它 → link 采集失败绝不影响上行, 更不允许崩 VM

自检增强 + 反向验证:
- tools/sim_load_test.lua 的 mobile 桩改为"只含官方 demo 证实存在的 API"(移除 msisdn):
  桩面必须与真实固件一致 —— "想当然"多写的桩会掩盖真实 bug
- 反向验证: 把 mobile.msisdn() 注射回代码 → 自检精确报
  "✗ link_info.get() [link_info.lua:53: attempt to call a nil value (field 'msisdn')]"(exit 1);
  还原后 exit 0 → 该自检可拦住整类"API 面假设错误"

验证: luac5.3 -p 全绿; Lua 离线自检通过; Python 帧协议验证通过; readme 表格列数 0 异常
文档: readme §8 补第二批修复记录, 修正 link.msisdn 字段来源说明
This commit is contained in:
wangfq
2026-09-10 15:44:47 +08:00
parent e8917e7041
commit 2f75f5abff
4 changed files with 79 additions and 12 deletions
@@ -147,7 +147,7 @@
| `imei` | mobile.imei() | 4G 模块 IMEI,设备唯一标识 |
| `iccid` | mobile.iccid() | **流量卡卡号**,物联网卡管理识别用(卡商未写入 → 空串) |
| `imsi` | mobile.imsi() | IMSI(部分卡返回空) |
| `msisdn` | mobile.msisdn() | 手机号(物联网卡通常拿不到 → 空串) |
| `msisdn` | `safe(mobile.number, 0)` | 手机号。⚠ Air780EPM **无 `mobile.msisdn()`**(实测直接调用崩 VM), 官方 API 为 `mobile.number(simid)`; 物联卡普遍无机号通常空串 |
| `csq` | mobile.csq() | 信号强度 0-31(31 最强,99/255 无信号) |
| `net` | 固定 "4G" | 网络制式(预留扩展) |
@@ -300,3 +300,31 @@ vd960Air/
- 新增 API 调用前先交叉核实 LuatOS 是否提供(如 `mstick` vs `mcu.ticks()`),不要"想当然"
> 基底来源: 合宙官方 Air780EPM demo/mqtt(main.lua / uart_app.lua / mqtt/* / network_watchdog / netdrv_device),MIT 许可。
### 2026-09-10 第二批: 修复后的下一处崩溃(link_info / mobile.msisdn)
**背景**: 第一批修复后, 模块成功注网并连上 MQTT(`IP_READY 10.38.28.79``conack nil nil``suback true 0`),
随即在**组装 initialize 报文时**崩溃:
```
E/user.coroutine.resume link_info.lua:29: attempt to call a nil value (field 'msisdn')
link_info.lua:29: in function 'link_info.get'
app_iot.lua:34: in upvalue 'build_initialize'
app_iot.lua:65: in function 'app_iot.on_connected'
E/main Lua VM exit!! reboot in 15000ms
```
**根因**: `mobile.msisdn()` **在 Air780EPM 上不存在**。LuatOS 各固件的 API 面不一致, 字段不存在时
**直接调用是"抛异常崩 VM"而非"返回 nil"** —— 与 `mstick()` 同性质。官方手机号 API 是
`mobile.number(simid)`(见 `demo/mobile/mobile_test.lua`), 且物联网卡普遍无机号。
**修复(防御性, 两层)**:
1. `link_info.lua`: 新增 `safe(fn, ...)` 助手(`type(fn)=="function"` 判断 + `pcall`), **所有 `mobile.*` 调用统一走它**
→ 字段缺失/调用异常一律返回 nil、不再抛出; `msisdn` 改用 `safe(mobile.number, 0)`; `csq` 兼容 table 返回
2. `app_iot.lua`: 新增 `get_link()` 兜底(内部 `pcall` + 失败时返回全空 link 表), `inject_link``build_initialize` 均改用它
**link 采集失败绝不影响上行, 更不允许崩 VM**
**验证(含反向验证)**: `tools/sim_load_test.lua` 的 mobile 桩改为"只含官方 demo 证实存在的 API"(移除 `msisdn`)。
反向验证: 把 `mobile.msisdn()` 注射回代码 → 自检精确报
`✗ link_info.get() [link_info.lua:53: attempt to call a nil value (field 'msisdn')]`(exit 1); 还原后 exit 0。
→ 该自检可拦住整类"API 面假设错误"。