现象(第一批修复后): 模块成功注网(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 字段来源说明
112 lines
3.8 KiB
Lua
112 lines
3.8 KiB
Lua
--[[
|
|
@module app_iot
|
|
@summary vd960Air 设备业务: initialize 上线 / heartbeat 心跳 / 主循环驱动
|
|
@version 0.1.1
|
|
@date 2026.08.31
|
|
@modify 2026.09.10 link 采集加 get_link() 兜底, mobile API 缺失不再崩 VM
|
|
@usage
|
|
- on_connected(): MQTT 连接成功后调用 → 发 initialize(extra_info imei/iccid + link)
|
|
- heartbeat 定时任务: 60s 发 heartbeat(协议 §5.4,带 link)
|
|
- poll(): 主循环驱动 uart_app 事件重发检查(可挂 sys.timerLoopStart)
|
|
]]
|
|
|
|
local CFG = require "config"
|
|
local link_info = require "link_info"
|
|
local clock = require "clock"
|
|
|
|
local app_iot = {}
|
|
|
|
local HEARTBEAT_INTERVAL = CFG.CFG_HEARTBEAT_INTERVAL or 60
|
|
|
|
-- 采集 link 信息(兜底: link 采集失败绝不影响上行, 更不允许崩 VM)
|
|
-- ⚠ 2026-09-10: link_info.get() 曾因调用不存在的 mobile.msisdn 抛异常导致
|
|
-- on_connected -> build_initialize 链路整机复位; 此处双层防护
|
|
local function get_link()
|
|
local ok, info = pcall(link_info.get)
|
|
if ok and type(info) == "table" then
|
|
return info
|
|
end
|
|
log.warn("app_iot", "link_info.get failed", info)
|
|
return { imei = "", iccid = "", imsi = "", msisdn = "", csq = 0, net = "4G" }
|
|
end
|
|
|
|
-- 上行 JSON 注入 link
|
|
local function inject_link(payload)
|
|
if CFG.CFG_LINK_ENABLE then
|
|
local ok, obj = pcall(json.decode, payload)
|
|
if ok and type(obj) == "table" then
|
|
obj.link = get_link()
|
|
return json.encode(obj)
|
|
end
|
|
end
|
|
return payload
|
|
end
|
|
|
|
-- 组 initialize(协议 §5.1,extra_info 含 imei/iccid V1.13)
|
|
local function build_initialize()
|
|
local info = get_link()
|
|
local payload = string.format(
|
|
'{"msg_id":1,"cmd":"initialize","ts":%d,'
|
|
.. '"data":{"dev_serial":"%s","model":"%s","hard_ver":"%s","soft_ver":"%s",'
|
|
.. '"loop_ver":"%s","loop_hw_ver":"%s",'
|
|
.. '"extra_info":{"code":"%s","imei":"%s","iccid":"%s","csq":"%s","location":""}}}',
|
|
clock.now(),
|
|
CFG.CFG_DEV_SERIAL, CFG.CFG_MODEL or "DLD960", CFG.CFG_HW_VER or "1.0", CFG.CFG_SW_VER or "1.0",
|
|
CFG.CFG_LOOP_VER or "", CFG.CFG_LOOP_HW_VER or "",
|
|
info.imsi, info.imei, info.iccid, tostring(info.csq))
|
|
return inject_link(payload)
|
|
end
|
|
|
|
-- 组 heartbeat(协议 §5.4)
|
|
local function build_heartbeat()
|
|
local uart_app = require "uart_app"
|
|
local loop_ok = uart_app.get_loop_status()
|
|
local ls = string.format('[%s,%s,%s,%s]',
|
|
tostring(loop_ok[1]), tostring(loop_ok[2]),
|
|
tostring(loop_ok[3]), tostring(loop_ok[4]))
|
|
local payload = string.format(
|
|
'{"msg_id":%d,"cmd":"heartbeat","ts":%d,'
|
|
.. '"data":{"uptime":%d,"loop_status":%s,"net_status":true,"iot_status":true}}',
|
|
math.floor(mstick() / 1000) % 100000 + 1000, clock.now(),
|
|
math.floor(mstick() / 1000), ls)
|
|
return inject_link(payload)
|
|
end
|
|
|
|
-- MQTT 连接成功后调用
|
|
function app_iot.on_connected()
|
|
-- 发 initialize(上线)
|
|
local payload = build_initialize()
|
|
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 1)
|
|
log.info("app_iot", "initialize sent")
|
|
end
|
|
|
|
-- 心跳定时任务
|
|
local function heartbeat_task()
|
|
while true do
|
|
sys.wait(HEARTBEAT_INTERVAL * 1000)
|
|
local payload = build_heartbeat()
|
|
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 0)
|
|
log.info("app_iot", "heartbeat sent")
|
|
end
|
|
end
|
|
|
|
-- 主循环驱动: 事件重发检查(500ms 粒度)
|
|
local function poll_task()
|
|
local uart_app = require "uart_app"
|
|
while true do
|
|
sys.wait(500)
|
|
uart_app.poll()
|
|
end
|
|
end
|
|
|
|
-- 初始化(evt_queue 复位 + 启动定时任务)
|
|
function app_iot.init()
|
|
local evt_queue = require "evt_queue"
|
|
evt_queue.init()
|
|
sys.taskInit(heartbeat_task)
|
|
sys.taskInit(poll_task)
|
|
log.info("app_iot", "init ok")
|
|
end
|
|
|
|
return app_iot
|