init: Air780EPM 官方 LuatOS 项目代码基线

- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
wangfq
2026-08-31 08:53:49 +08:00
commit 173dd6874f
813 changed files with 125484 additions and 0 deletions
@@ -0,0 +1,564 @@
--[[
@module aircloud
@summary 合宙IoT平台(excloud) + MQTT 双通道数据上报模块
@version 1.0
@date 2026.07.23
@author 江访
@usage
本文件为合宙IoT平台(excloud)和MQTT双通道数据上报功能模块,核心业务逻辑为:
1、excloud初始化、连接认证、心跳维护
2、通过excloud上报传感器数据和FOTA信息
3、通过MQTT短连接上报JSON格式完整数据
本文件的对外接口有5个:
1、aircloud.init(cfg):初始化excloud并等待连接/认证完成
2、aircloud.report_excloud(sensor_data, fota_info):通过excloud上报数据
3、aircloud.report_fota_status(status_text, sensor_data):通过excloud上报FOTA状态
4、aircloud.report_mqtt(topic, payload, broker, port):通过MQTT短连接上报JSON数据
5、aircloud.build_mqtt_payload(sensor_data, fota_info):构建MQTT上报payload
]]
-- ==================== 加载扩展库 ====================
-- excloud: 合宙IoT平台通信扩展库,基于自有TLV协议
-- 支持TCP和MQTT两种传输方式,本模块使用TCP方式
local excloud = require "excloud"
-- ==================== 模块表 ====================
-- aircloud 为模块对外接口表,所有对外函数注册在此表上
local aircloud = {}
-- ==================== 内部状态 ====================
-- g_mqtt_publish_done: MQTT发布是否已完成(成功或失败)
-- g_mqtt_publish_success: MQTT发布是否成功
-- 这两个变量用于MQTT短连接模式下的同步等待
local g_mqtt_publish_done = false
local g_mqtt_publish_success = false
-- ==================== MQTT事件回调 ====================
--[[
MQTT客户端事件回调处理函数
本回调注册到 mqtt_client:on(),处理以下事件:
conack — MQTT连接成功(broker确认)
sent — 数据发布成功
disconnect — MQTT连接断开(正常或异常)
error — MQTT通信错误
@param userdata mqtt_client MQTT客户端对象(由mqtt.create返回)
@param string event 事件类型
"conack" — TCP连接成功且收到broker的CONNACK
"sent" — PUBLISH报文已发送完成
"disconnect" — 连接断开(包括主动disconnect和被动断开)
"error" — 通信过程发生错误
@param any data 事件附加数据
conack事件: nil
sent事件: 报文标识符
disconnect事件: 断开原因码
error事件: 错误描述字符串
@param string payload 收到的消息负载(仅message事件有值,本模块未使用)
@param table metas 消息元数据(仅message事件有值,本模块未使用)
]]
local function mqtt_event_callback(mqtt_client, event, data, payload, metas)
log.info("aircloud", "MQTT事件:", event, data)
if event == "conack" then
-- MQTT连接成功:发布MQTT_CONNECT_OK消息,携带客户端对象
-- 上层等待MQTT_CONNECT_OK后获取connected_client进行publish
log.info("aircloud", "MQTT连接成功")
sys.publish("MQTT_CONNECT_OK", mqtt_client)
elseif event == "sent" then
-- 数据发送成功:标记发布完成并发布MQTT_SENT_OK
log.info("aircloud", "MQTT数据发送成功")
g_mqtt_publish_done = true
g_mqtt_publish_success = true
sys.publish("MQTT_SENT_OK")
elseif event == "disconnect" then
-- 连接断开:标记发布完成(未成功),防止上层永久等待
log.warn("aircloud", "MQTT断开")
g_mqtt_publish_done = true
sys.publish("MQTT_SENT_OK")
elseif event == "error" then
-- 通信错误:标记发布完成(未成功)
log.warn("aircloud", "MQTT错误:", data)
g_mqtt_publish_done = true
sys.publish("MQTT_SENT_OK")
end
end
-- ==================== excloud事件回调 ====================
--[[
excloud平台事件回调处理函数
本回调注册到 excloud.on(),处理以下事件:
connect_result — TCP连接结果
auth_result — 设备认证结果
disconnect — 与服务器断开连接
send_result — 数据发送结果
@param string event 事件类型
"connect_result" — 与excloud服务器的TCP连接结果
"auth_result" — 通过auth_key认证的结果
"disconnect" — 与服务器断开连接
"send_result" — TLV数据发送结果
@param table data 事件数据
connect_result: {success=boolean, error=string}
auth_result: {success=boolean, message=string}
disconnect: 空table
send_result: {success=boolean, sequence_num=number, error_msg=string}
]]
local function excloud_event_callback(event, data)
if event == "connect_result" then
-- TCP连接结果
-- data.success=true 表示TCP三次握手成功
-- 后续会进行auth认证
if data.success then
log.info("aircloud", "excloud连接成功")
else
log.warn("aircloud", "excloud连接失败:", data.error or "未知")
end
elseif event == "auth_result" then
-- 设备认证结果
-- data.success=true 表示通过auth_key认证通过
-- 认证通过后发布EXCLOUD_READY,通知上层可以开始数据交互
if data.success then
log.info("aircloud", "excloud认证成功")
sys.publish("EXCLOUD_READY")
else
log.warn("aircloud", "excloud认证失败:", data.message)
end
elseif event == "disconnect" then
-- 与服务器断开连接
log.warn("aircloud", "excloud断开连接")
elseif event == "send_result" then
-- TLV数据发送结果
-- data.sequence_num 为消息流水号,用于日志追踪
if data.success then
log.info("aircloud", "excloud发送成功, 流水号:", data.sequence_num)
else
log.warn("aircloud", "excloud发送失败:", data.error_msg)
end
end
end
-- ==================== API:初始化excloud ====================
--[[
初始化excloud连接并等待认证完成
aircloud.init(cfg)
本函数完成以下工作:
1、注册事件回调 excloud_event_callback
2、调用 excloud.setup 配置传输参数
3、调用 excloud.open 开启服务
4、启动自动心跳(间隔300秒)
5、等待认证完成(最长20秒)
注意:
1、本函数内部有 sys.waitUntil(),必须在协程中调用
2、如果超时(20秒未认证成功),认为初始化失败
3、excloud.setup 已不再需要主动配置 use_getip 和 device_type 参数
@param table cfg
含义:excloud配置参数表
是否必选:必选
cfg包含字段:
transport : string, 传输协议类型, 默认"tcp"
"tcp" — TCP长连接,适合频繁上报场景
"mqtt" — MQTT协议,适合标准MQTT生态
auto_reconnect : boolean, 是否自动重连, 默认true
true — 断开后自动尝试重连
false — 断开后不自动重连
reconnect_interval: number, 重连间隔(秒), 默认10
取值范围:5~300
max_reconnect : number, 最大重连次数, 默认3
达到最大次数后不再重连
mtn_log_enabled : boolean, 是否启用运维日志上传, 默认false
@return boolean
含义:初始化是否成功(含TCP连接和认证)
true — excloud已就绪,可进行数据上报
false — 初始化失败,可能是网络问题或PRODUCT_KEY无效
@usage
local ok = aircloud.init({
transport = "tcp",
auto_reconnect = true,
reconnect_interval = 10,
max_reconnect = 3,
})
if not ok then
log.error("aircloud", "初始化失败,跳过excloud上报")
end
]]
function aircloud.init(cfg)
log.info("aircloud", "正在初始化excloud...")
-- 第一步:注册事件回调
-- excloud扩展库内部维护此回调,收到平台事件时回调本函数
excloud.on(excloud_event_callback)
-- 第二步:配置excloud参数
-- excloud.setup会自动通过PRODUCT_KEY获取服务器地址和端口
-- 不再需要手动指定host和port
local ok, err_msg = excloud.setup({
transport = cfg.transport or "tcp",
auto_reconnect = (cfg.auto_reconnect ~= false),
reconnect_interval = cfg.reconnect_interval or 10,
max_reconnect = cfg.max_reconnect or 3,
mtn_log_enabled = cfg.mtn_log_enabled or false,
})
if not ok then
log.error("aircloud", "excloud.setup失败:", err_msg)
return false
end
-- 第三步:开启excloud服务
-- open()会发起TCP连接到服务器
ok, err_msg = excloud.open()
if not ok then
log.error("aircloud", "excloud.open失败:", err_msg)
return false
end
-- 第四步:启动自动心跳
-- 默认间隔300秒,保持TCP长连接活跃
excloud.start_heartbeat()
-- 第五步:等待认证完成,最长20秒
-- EXCLOUD_READY由excloud_event_callback在auth_result成功时发布
sys.waitUntil("EXCLOUD_READY", 20000)
return true
end
-- ==================== APIexcloud数据上报 ====================
--[[
通过excloud平台上报传感器数据和FOTA信息
aircloud.report_excloud(sensor_data, fota_info)
本函数构建TLV列表并通过excloud.send上报服务器。
TLV字段使用 CONTROL_RESPONSE(自定义数据)类型。
@param table sensor_data
含义:传感器数据表(由sensor_vl53l1x.collect_data返回)
是否必选:可选
传入nil表示不上报传感器数据
当传入非nil时,上报 sensor_data.avg_distance 作为TEXT类型值
@param string fota_info
含义:FOTA升级信息描述字符串
是否必选:可选
传入nil表示不上报FOTA信息
示例值:
"FOTA升级成功: 升级前: VERSION=001.999.000 ; 升级后: VERSION=001.999.001"
"FOTA升级包下载成功, 升级前版本: 001.999.000"
@return boolean
含义:上报是否成功
true — 数据已通过TCP发送到服务器
false — excloud未连接或发送失败
@usage
-- 上报传感器数据
aircloud.report_excloud(sensor_data, nil)
-- 上报传感器数据+FOTA信息
aircloud.report_excloud(sensor_data, "FOTA升级成功: xxx")
-- 仅上报FOTA信息
aircloud.report_excloud(nil, "FOTA升级包下载成功")
]]
function aircloud.report_excloud(sensor_data, fota_info)
-- 检查excloud连接状态
local status = excloud.status()
if not status or not status.is_connected then
log.warn("aircloud", "excloud未连接,跳过数据上报")
return false
end
-- 构建TLV列表
-- TLV (Tag-Length-Value) 是excloud协议的数据单元格式
-- field_meaning 用 CONTROL_RESPONSE(0x14) 表示自定义数据
-- data_type 使用 UNICODE/String 类型
local tlvs = {}
-- 添加传感器数据TLV
if sensor_data then
tlvs[#tlvs + 1] = {
field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
data_type = excloud.DATA_TYPES.UNICODE,
value = tostring(sensor_data.avg_distance),
}
end
-- 添加FOTA信息TLV
if fota_info then
tlvs[#tlvs + 1] = {
field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
data_type = excloud.DATA_TYPES.UNICODE,
value = fota_info,
}
end
-- 没有TLV时直接返回失败
if #tlvs == 0 then
return false
end
-- 发送TLV列表(不需要服务器回复)
-- 第二个参数false表示不需要服务器回复ACK
local ok, err_msg = excloud.send(tlvs, false)
if ok then
log.info("aircloud", "excloud数据上报成功")
else
log.warn("aircloud", "excloud数据上报失败:", err_msg)
end
return ok
end
-- ==================== APIexcloud FOTA状态上报 ====================
--[[
通过excloud上报FOTA状态文本(升级中状态)
aircloud.report_fota_status(status_text, sensor_data)
本函数专门用于上报"FOTA正在检查/升级中"等过程状态。
与report_excloud的区别在于本函数强制要求status_text不为空。
@param string status_text
含义:FOTA状态描述文本
是否必选:必选
示例值:
"FOTA: 正在检查/升级中"
"FOTA: 下载进度 50%"
@param table sensor_data
含义:传感器数据(可选,附带上报)
是否必选:可选
@return boolean
含义:上报是否成功
@usage
aircloud.report_fota_status("FOTA: 正在检查/升级中", sensor_data)
]]
function aircloud.report_fota_status(status_text, sensor_data)
-- 检查excloud连接状态
local status = excloud.status()
if not status or not status.is_connected then
log.warn("aircloud", "excloud未连接,跳过FOTA状态上报")
return false
end
-- 构建TLV列表
local tlvs = {}
-- 可选:附带传感器数据
if sensor_data then
tlvs[#tlvs + 1] = {
field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
data_type = excloud.DATA_TYPES.UNICODE,
value = tostring(sensor_data.avg_distance),
}
end
-- 必选:FOTA状态文本
tlvs[#tlvs + 1] = {
field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
data_type = excloud.DATA_TYPES.UNICODE,
value = status_text,
}
-- 发送TLV列表
local ok, err_msg = excloud.send(tlvs, false)
if ok then
log.info("aircloud", "FOTA状态已上报:", status_text)
else
log.warn("aircloud", "FOTA状态上报失败:", err_msg)
end
return ok
end
-- ==================== APIMQTT数据上报 ====================
--[[
通过MQTT短连接上报JSON格式数据
aircloud.report_mqtt(topic, payload_table, mqtt_broker, mqtt_port)
本函数每次调用都会创建一个新的MQTT短连接:
创建客户端 → 连接broker → 发布数据 → 断开连接 → 释放客户端
这样可以避免维护长连接,但每次有额外的TCP握手开销。
MQTT认证方式:client_id = IMEI + "_vl53l1x"
@param string topic
含义:MQTT发布主题
取值范围:任意合法的MQTT主题字符串
是否必选:必选
示例值:"864317083866528/vl53l1x/up"
@param table payload_table
含义:待上报的数据表(会被json.encode编码为JSON字符串)
取值范围:任意可被json.encode序列化的table
是否必选:必选
示例值:{distance_mm=265, project="Air8780P_VL53L1X"}
@param string mqtt_broker
含义:MQTT服务器地址(域名或IP)
是否必选:必选
示例值:"lbsmqtt.airm2m.com"
@param number mqtt_port
含义:MQTT服务器端口号
取值范围:1~65535
是否必选:可选
默认值:1884
@return boolean
含义:上报是否成功
true — 数据已成功发布到broker
false — 连接失败或发布失败
@usage
local payload = {distance_mm=265, project="Air8780P_VL53L1X"}
aircloud.report_mqtt("IMEI/vl53l1x/up", payload, "lbsmqtt.airm2m.com", 1884)
]]
function aircloud.report_mqtt(topic, payload_table, mqtt_broker, mqtt_port)
-- 参数有效性检查
if not topic or not payload_table or not mqtt_broker then
return false
end
log.info("aircloud", "MQTT上报数据到:", topic)
-- 重置MQTT同步变量
g_mqtt_publish_done = false
g_mqtt_publish_success = false
-- 第一步:创建MQTT客户端
-- mqtt.create(adapter, host, port, ssl)
-- adapter传nil使用默认网卡
local mqtt_client = mqtt.create(nil, mqtt_broker, mqtt_port or 1884)
if not mqtt_client then
log.error("aircloud", "MQTT创建失败")
return false
end
-- 第二步:设置认证信息和事件回调
-- client_id使用IMEI保证唯一性
-- username/password为空表示匿名登录
-- clean_session=false表示不清理session
mqtt_client:auth(mobile.imei() .. "_vl53l1x", "", "", false)
mqtt_client:on(mqtt_event_callback)
-- 第三步:发起TCP连接
if not mqtt_client:connect() then
log.error("aircloud", "MQTT连接失败")
mqtt_client:close()
return false
end
-- 第四步:等待连接确认(CONNACK),最长5秒
local no_timeout, connected_client = sys.waitUntil("MQTT_CONNECT_OK", 5000)
if not no_timeout or not connected_client then
log.warn("aircloud", "MQTT连接超时")
mqtt_client:disconnect()
sys.wait(500)
mqtt_client:close()
return false
end
-- 第五步:发布数据
-- qos=1, retain=0
local payload_json = json.encode(payload_table)
connected_client:publish(topic, payload_json, 1, 0)
log.info("aircloud", "MQTT已发布:", payload_json)
-- 第六步:等待发布结果,最长8秒
sys.waitUntil("MQTT_SENT_OK", 8000)
-- 第七步:断开连接并释放资源
connected_client:disconnect()
sys.wait(500)
connected_client:close()
log.info("aircloud", "MQTT短连接结束,发送" .. (g_mqtt_publish_success and "成功" or "失败"))
return g_mqtt_publish_success
end
-- ==================== API:构建MQTT payload ====================
--[[
构建MQTT上报的JSON payload数据表
aircloud.build_mqtt_payload(sensor_data, fota_info)
本函数生成统一格式的MQTT上报数据,包含以下通用字段:
imei — 设备IMEI号,用于设备识别
project — 项目名称,用于区分不同项目的数据
version — 脚本版本号,用于追踪固件版本
ts — Unix时间戳,记录数据采集时间
@param table sensor_data
含义:传感器数据表(由sensor_vl53l1x.collect_data返回)
是否必选:可选
当传入非nil时,payload增加以下字段:
distance_mm — 平均距离(number
distances — 原始距离数组(table)
valid_samples— 有效帧数(number
@param string fota_info
含义:FOTA升级信息(可选)
是否必选:可选
当传入非nil时,payload增加以下字段:
fota_info — FOTA升级描述字符串
@return table
含义:MQTT上报用的数据表,可直接传入json.encode
@usage
local payload = aircloud.build_mqtt_payload(sensor_data, "FOTA升级成功")
-- 返回: {imei="xxx", project="Air8780P_VL53L1X", version="001.999.000",
-- ts=1784623861, distance_mm=265, distances={267,261,267},
-- valid_samples=3, fota_info="FOTA升级成功"}
]]
function aircloud.build_mqtt_payload(sensor_data, fota_info)
-- 基础payload:所有上报必含设备标识和时间戳
local payload = {
imei = mobile.imei(),
project = PROJECT,
version = VERSION,
ts = os.time(),
}
-- 可选:添加传感器数据字段
if sensor_data then
payload.distance_mm = sensor_data.avg_distance
payload.distances = sensor_data.distance_list
payload.valid_samples = sensor_data.valid_count
end
-- 可选:添加FOTA信息字段
if fota_info then
payload.fota_info = fota_info
end
return payload
end
return aircloud
@@ -0,0 +1,64 @@
--[[
@module drv_led
@summary LED指示灯驱动配置功能模块
@version 1.0
@date 2026.07.23
@author 江访
@usage
本文件为LED指示灯驱动配置功能模块,提供了LED灯在正常工作状态和PSM+休眠状态下的控制逻辑。
LED状态说明:
1、正常工作状态(LED点亮):设备初始化完成、数据采集中、网络通信中
2、PSM+模式(LED熄灭):进入深度休眠前关闭,降低功耗
本文件的对外接口有2个:
1、sys.subscribe("LED_SET_HIGH", led_set_high_func):拉高LED指示灯
2、sys.subscribe("LED_SET_LOW", led_set_low_func):拉低LED指示灯
其他模块通过 sys.publish 控制LED
sys.publish("LED_SET_HIGH", gpio_led) -- 拉高LED
sys.publish("LED_SET_LOW", gpio_led) -- 拉低LED
]]
-- ==================== LED拉高事件处理 ====================
--[[
LED拉高事件处理函数
订阅LED_SET_HIGH消息,收到后拉高指定GPIO引脚。
LED点亮表示设备处于正常工作状态。
@param number gpio_led GPIO引脚号(例如27
]]
local function led_set_high_func(gpio_led)
if not gpio_led then return end
gpio.setup(gpio_led, 1)
gpio.set(gpio_led, 1)
log.info("drv_led", "GPIO" .. gpio_led .. " 拉高")
end
-- ==================== LED拉低事件处理 ====================
--[[
LED拉低事件处理函数
订阅LED_SET_LOW消息,收到后拉低指定GPIO引脚。
LED熄灭表示设备即将进入低功耗模式。
@param number gpio_led GPIO引脚号(例如27
]]
local function led_set_low_func(gpio_led)
if not gpio_led then return end
gpio.set(gpio_led, 0)
log.info("drv_led", "GPIO" .. gpio_led .. " 拉低")
end
-- ==================== 事件订阅 ====================
sys.subscribe("LED_SET_HIGH", led_set_high_func)
sys.subscribe("LED_SET_LOW", led_set_low_func)
log.info("drv_led", "模块已加载,等待LED_SET_HIGH/LED_SET_LOW消息")
-- 如果项目启动后需要LED保持某种状态,直接在此处配置初始状态
-- 此处的配置取决于硬件设计,合宙核心板上电后GPIO默认状态为高阻态
@@ -0,0 +1,92 @@
--[[
@module drv_psm
@summary PSM+模式(pm.power WORK_MODE 3)驱动配置功能模块
@version 1.0
@date 2026.07.23
@author 江访
@usage
本文件为PSM+模式驱动配置功能模块,提供了PSM+模式的配置模板,包括以下几点:
1、在进入PSM+模式前,配置深度休眠定时器(dtimer),设定唤醒时间
2、提供进入PSM+模式的入口函数,包括pm.power(pm.WORK_MODE, 3)、等待和重启兜底
3、在进入PSM+模式前,可以根据实际项目需求配置其他功能引脚
PSM+模式说明:
1、pm.power(pm.WORK_MODE, 3)配置后,并不会立即进入休眠
2、等所有协程都处于阻塞状态时,才会自动进入PSM+模式
3、进入PSM+模式后,RAM掉电,不执行任何脚本代码
4、RTC定时器到达或外部中断唤醒后,系统冷启动
本文件的对外接口只有1个:
1、sys.subscribe("DRV_SET_PSM", set_drv_psm):订阅"DRV_SET_PSM"消息;
其他模块如果需要进入PSM+模式,先发布"DRV_SET_PSM"消息,然后阻塞本task即可。
]]
-- ==================== PSM+入口主函数 ====================
--[[
PSM+模式任务函数
订阅DRV_SET_PSM消息后,在task协程中执行此函数。
函数流程:
1、配置深度休眠定时器(设定唤醒时间)
2、调用pm.power(pm.WORK_MODE, 3)进入PSM+模式
3、等待80秒——如果未成功进入PSM+,强制重启
注意:
1、必须在调用前关闭所有外设(传感器等)
2、必须在调用前拉低LED
3、进入PSM+前需要先配置好深度休眠定时器dtimer
4、PSM+模式下,所有引脚状态保持进入前的电平
@param number sleep_min_s PSM+休眠时间(分钟)
]]
local function psm_task(sleep_min_s)
log.info("drv_psm", "进入PSM+模式,休眠" .. sleep_min_s .. "分钟后唤醒")
-- 设置RTC深度休眠定时器
-- pm.dtimerStart(id, ms)
-- id=0表示使用定时器0,到达时间后系统自动唤醒
-- 此处的时长不要小于80秒
local sleep_ms = sleep_min_s * 60 * 1000
pm.dtimerStart(0, sleep_ms)
log.info("drv_psm", "深度休眠定时器已设置:", sleep_ms, "ms")
-- 在进入PSM+模式前,可以根据实际项目需求配置以下功能项:
-- 1、飞行模式:PSM+模式下飞行模式自动开启,无需手动操作
-- 2、USB功能:内核固件已自动关闭USB功能(2025年3月后版本)
-- 3、AGPIO配置:根据硬件设计配置GPIO24~28的电平状态
-- 详情可参考 lowpower/drv/drv_psm.lua 中的 set_psm_func_item()
-- 配置最低功耗模式为PSM+模式
-- pm.power(pm.WORK_MODE, 3)表示允许系统进入PSM+模式
-- 执行后不会立即进入休眠,等所有协程阻塞后自动进入
pm.power(pm.WORK_MODE, 3)
-- 等待80秒:给内核固件足够时间进入PSM+
-- 如果成功进入PSM+,此后的代码不会执行(RAM掉电)
-- 如果没有进入(例如有协程未阻塞),80秒后强制重启
sys.wait(80000)
log.info("drv_psm", "进入PSM+失败,重启")
rtos.reboot()
end
-- ==================== 事件处理 ====================
--[[
DRV_SET_PSM 事件处理函数
其他模块通过 sys.publish("DRV_SET_PSM", sleep_min_s) 触发:
sleep_min_s参数为休眠时间(分钟)
注意:本订阅处理函数使用sys.taskInit创建独立协程执行psm_task
这是因为pm.power和内部sys.wait需要在协程中运行
]]
local function set_drv_psm(sleep_min_s)
sys.taskInit(psm_task, sleep_min_s or 15)
end
-- ==================== 事件订阅 ====================
sys.subscribe("DRV_SET_PSM", set_drv_psm)
log.info("drv_psm", "模块已加载,等待DRV_SET_PSM消息")
@@ -0,0 +1,16 @@
--[[
@module netdrv_device
@summary 网络驱动设备功能模块
@version 1.0
@date 2026.07.21
@author 江访
@usage
本文件为网络驱动设备功能模块,核心业务逻辑为:
根据Air8780P项目需求,选择并且配置合适的网卡(网络适配器)
本demo使用4G网卡(LWIP_GP
本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行
]]
-- 加载"4G网卡"驱动模块
require "netdrv_4g"
@@ -0,0 +1,331 @@
--[[
@module fota_mgr
@summary libfota2远程升级管理模块,含版本文件管理和首次启动检测
@version 1.0
@date 2026.07.23
@author 江访
@usage
本文件为libfota2远程升级的管理模块,核心业务逻辑为:
1、FOTA升级检查与下载
2、版本文件管理(保存/读取)
3、FOTA升级后的首次启动检测
升级流程:
1. 调用 check_and_upgrade() 检查是否有新版本
2. 有新版 → 下载升级包 → 下载成功返回 true → 调用方重启设备
3. 重启后首次运行 → check_first_boot() 检测到版本不一致 → 上报升级结果
4. 上报完成后调用 clear_version_file() 清除标记
本文件的对外接口有4个:
1、fota_mgr.check_first_boot():检测是否为FOTA升级后的首次启动
2、fota_mgr.check_and_upgrade(max_wait_s):检查并执行FOTA升级
3、fota_mgr.get_last_result():获取最后一次FOTA结果
4、fota_mgr.clear_version_file():清除版本标记文件
]]
-- ==================== 加载扩展库 ====================
-- libfota2: 合宙FOTA升级扩展库
-- 实现HTTP下载升级包的底层逻辑,自动拼接URL和版本号
-- 依赖PRODUCT_KEY在main.lua中定义
local libfota2 = require "libfota2"
-- ==================== 模块表 ====================
-- fota_mgr 为模块对外接口表,所有对外函数注册在此表上
local fota_mgr = {}
-- ==================== 内部状态 ====================
-- g_fota_running: FOTA升级是否正在进行中
-- 用于防止重复调用
local g_fota_running = false
-- g_fota_result: FOTA回调返回码
-- nil=尚未执行,0=成功下载,非0=各种失败原因
-- 用于任务1(PSM+超时管理)判断是否需要等待
local g_fota_result = nil
-- G_FOTA_FILE: 版本标记文件的路径
-- 文件内容格式: "VERSION,PROJECT"
-- 例如: "001.999.000,Air8780P_VL53L1X"
-- 用于检测FOTA升级后的首次启动
local G_FOTA_FILE = "/fota_version.txt"
-- ==================== FOTA回调 ====================
--[[
libfota2升级回调处理函数
当libfota2框架完成升级检查或下载后,自动调用此回调。
回调参数 ret 表示FOTA状态码。
回调完成后发布FOTA_END消息,通知PSM+管理任务:
1、如果 ret==0(成功下载),任务1看到后会让系统进入重启流程
2、如果 ret!=0(失败或无升级),任务1在等待超时后进入PSM+
@param number ret FOTA返回码
0 — 升级包下载成功,需要重启以应用升级
1 — 连接FOTA服务器失败(网络不通或DNS解析失败)
2 — 升级包URL错误(PRODUCT_KEY不匹配)
3 — 与FOTA服务器的连接断开
4 — 接收报文错误(下载不完整或校验失败),也可能是已是最新版本
5 — 版本号格式错误(VERSION不符合XXX.YYY.ZZZ格式)
]]
local function fota_callback(ret)
log.info("fota_mgr", "FOTA回调 ret=", ret)
-- 清除运行中标志
g_fota_running = false
g_fota_result = ret
-- 根据返回码打印详细日志
-- 参考libfota2文档中各返回码的含义
if ret == 0 then
log.info("fota_mgr", "FOTA升级包下载成功,准备重启")
elseif ret == 1 then
log.info("fota_mgr", "FOTA连接失败")
elseif ret == 2 then
log.info("fota_mgr", "FOTA URL错误")
elseif ret == 3 then
log.info("fota_mgr", "FOTA服务器断开")
elseif ret == 4 then
log.info("fota_mgr", "FOTA接收报文错误或已是最新版本")
elseif ret == 5 then
log.info("fota_mgr", "FOTA版本号格式错误")
else
log.info("fota_mgr", "FOTA未知返回码", ret)
end
-- 发布FOTA结束消息,通知PSM+管理任务(任务1)
-- 任务1等待此消息后检查g_fota_result,决定是否重启或进入PSM+
sys.publish("FOTA_END")
end
-- ==================== 版本文件管理(内部函数) ====================
--[[
保存当前版本信息到标记文件
文件中保存当前PROJECT和VERSION,格式:VERSION,PROJECT
下次启动时通过read_version读取并比对,判断是否为FOTA后的首次启动
文件路径:/fota_version.txt(存储在文件系统中,重启后保留)
]]
local function save_version()
local info = VERSION .. "," .. PROJECT
io.writeFile(G_FOTA_FILE, info)
log.info("fota_mgr", "保存版本信息到文件:", info)
end
--[[
从标记文件读取版本信息
@return string 文件内容,格式"VERSION,PROJECT"
例如:"001.999.000,Air8780P_VL53L1X"
@return nil 文件不存在或内容为空
首次运行时文件不存在,属于正常情况
]]
local function read_version()
local data = io.readFile(G_FOTA_FILE)
if data and #data > 0 then
return data
end
return nil
end
-- ==================== API:检测FOTA后首次启动 ====================
--[[
检测是否为FOTA升级后的首次启动
fota_mgr.check_first_boot()
本函数通过比对标记文件中的版本号与当前运行版本号来判断:
1、如果标记文件不存在 → 首次运行,保存版本信息,返回nil
2、如果版本号一致 → 正常启动,更新标记文件,返回nil
3、如果版本号不一致 → FOTA升级后首次启动,返回升级对比信息
本函数应在main.lua中require完成后尽早调用,以便在数据上报时包含升级信息。
调用后会自动更新标记文件为当前版本。
@return string
含义:若为FOTA后首次启动,返回包含升级前后版本信息的字符串
示例值:
"升级前: PROJECT=Air8780P_VL53L1X VERSION=001.999.000 ; 升级后: PROJECT=Air8780P_VL53L1X VERSION=001.999.001"
@return nil
含义:非FOTA首次启动(正常启动或首次运行)
@usage
local fota_info = fota_mgr.check_first_boot()
if fota_info then
log.info("fota_mgr", "检测到FOTA升级:", fota_info)
-- 后续数据上报时包含此信息
end
]]
function fota_mgr.check_first_boot()
-- 读取上次保存的版本信息
local old_info = read_version()
-- 文件不存在说明是第一次运行,直接保存当前版本
if not old_info then
save_version()
return nil
end
-- 解析文件内容:格式为 "VERSION,PROJECT"
-- 使用逗号分隔
local comma_pos = string.find(old_info, ",")
if not comma_pos then
-- 文件格式异常,覆盖写入当前版本
save_version()
return nil
end
-- 提取升级前的版本号和项目名
local old_version = string.sub(old_info, 1, comma_pos - 1)
local old_project = string.sub(old_info, comma_pos + 1)
-- 比对版本号或项目名是否发生变化
if old_version ~= VERSION or old_project ~= PROJECT then
-- 版本变化:说明经历了FOTA升级并重启
local result_str = string.format(
"升级前: PROJECT=%s VERSION=%s ; 升级后: PROJECT=%s VERSION=%s",
old_project, old_version, PROJECT, VERSION
)
log.info("fota_mgr", "检测到FOTA升级后的首次启动", result_str)
save_version()
return result_str
else
-- 版本一致:正常启动
save_version()
return nil
end
end
-- ==================== API:获取FOTA结果 ====================
--[[
获取最后一次FOTA回调的结果
fota_mgr.get_last_result()
本函数供PSM+管理任务(任务1)读取FOTA结果。
任务1在收到FOTA_END消息后调用此函数判断是否需要重启:
0 → FOTA成功,重启设备
非0 → FOTA失败或无更新,进入PSM+
@return number
含义:FOTA返回码
nil — 尚未执行过FOTA检查
0 — 升级包下载成功
1 — 连接失败
2 — URL错误
3 — 服务器断开
4 — 接收报文错误或已是最新版本
5 — 版本号格式错误
@usage
if fota_mgr.get_last_result() == 0 then
log.info("fota_mgr", "FOTA成功,准备重启")
end
]]
function fota_mgr.get_last_result()
return g_fota_result
end
-- ==================== API:执行FOTA升级 ====================
--[[
检查并执行FOTA升级
fota_mgr.check_and_upgrade(max_wait_s)
本函数完成以下工作:
1、保存当前版本信息到标记文件
2、发布FOTA_UPGRADING消息通知PSM+任务延长等待
3、调用libfota2.request发起升级检查/下载
4、等待FOTA结果,最长max_wait_s秒
5、返回是否需要重启
注意:
1、libfota2内部会自动检查是否需要升级
2、如果有新版本,libfota2会自动下载
3、下载完成后fota_callback会被调用,ret=0表示下载成功
4、本函数返回true后,调用方需执行rtos.reboot()
@param number max_wait_s
含义:FOTA等待最长时间(秒)
取值范围:正整数
如果网络状况好,通常10~30秒可完成
建议设置300~600秒(5~10分钟),为弱网环境留出重试时间
是否必选:必选
建议值:600
@return boolean
含义:true=升级包已下载需要重启,false=无升级或升级失败
@usage
local need_reboot = fota_mgr.check_and_upgrade(600)
if need_reboot then
rtos.reboot()
end
]]
function fota_mgr.check_and_upgrade(max_wait_s)
log.info("fota_mgr", "开始检查FOTA升级...")
-- 第一步:保存当前版本(用于下次启动时比对)
save_version()
g_fota_running = true
-- 第二步:通知PSM+管理任务(任务1)延长等待
-- 任务1原本在psm_entry_max_s秒后强制进PSM+
-- 收到FOTA_UPGRADING后会改为等待FOTA_END
sys.publish("FOTA_UPGRADING")
-- 第三步:发起FOTA请求
-- libfota2.request内部流程:
-- 1. 构造URLHTTP GET iot.openluat.com/api/...
-- 2. 下载升级包(如果有)
-- 3. 下载完成后调用fota_callback
-- opts可以传自定义参数,此处传空table使用默认配置
local opts = {}
libfota2.request(fota_callback, opts)
-- 第四步:等待FOTA结果,最长max_wait_s秒
-- 如果超时仍未收到FOTA_END,说明网络不好
sys.waitUntil("FOTA_END", max_wait_s * 1000)
-- 第五步:判断结果
if g_fota_result == 0 then
log.info("fota_mgr", "FOTA升级包已下载,需要重启模块")
return true
end
log.info("fota_mgr", "FOTA检查完成(无升级或升级失败)")
return false
end
-- ==================== API:清除版本标记文件 ====================
--[[
清除版本标记文件
fota_mgr.clear_version_file()
在以下场景需要调用:
1、FOTA升级后首次启动,上报完成升级信息后
2、无网络时跳过上报,需要清除文件以免下次启动再次上报
3、版本文件已不再需要时
清除后,下次启动时check_first_boot()会认为是最新版本,不会再返回FOTA信息。
@usage
fota_mgr.clear_version_file()
]]
function fota_mgr.clear_version_file()
os.remove(G_FOTA_FILE)
log.info("fota_mgr", "已删除版本标记文件")
end
return fota_mgr
@@ -0,0 +1,76 @@
--[[
@module main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date 2026.07.23
@author 江访
@usage
本demo演示Air8780P工业模组+VL53L1X激光测距传感器的低功耗数据采集与上报流程:
1、VL53L1X激光测距传感器数据采集
2、通过合宙iot平台(excloud)和MQTT上报传感器数据
3、支持libfota2远程升级
4、PSM+低功耗模式
更多说明参考本目录下的readme.md文件
]]
--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
-- main.lua - 程序入口文件
PROJECT = "Air8780P_VL53L1X"
VERSION = "001.999.000"
PRODUCT_KEY = "PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ" -- 用于合宙IOT平台FOTA升级,此处仅为示例,实际使用请修改为自己的PRODUCT_KEY
-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)
-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
if errDump then
errDump.config(true, 600)
end
-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用
-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
-- log.info("mem.lua", rtos.meminfo())
-- log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)
-- 加载网络驱动设备功能模块(4G网卡)
require "netdrv_device"
-- 加载VL53L1X项目编排模块
-- 该模块会级联加载:
-- drv/drv_led — 驱动层:LED控制(事件驱动)
-- drv/drv_psm — 驱动层:PSM+模式(事件驱动)
-- sensor/sensor_vl53l1x — 应用层:传感器驱动
-- cloud/aircloud — 应用层:云平台上报
-- fota/fota_mgr — 应用层:FOTA升级管理
require "prj_vl53l1x"
-- 用户代码已结束
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
@@ -0,0 +1,34 @@
--[[
@module netdrv_4g
@summary "4G网卡"驱动模块
@version 1.0
@date 2026.07.21
@author 江访
@usage
本文件为4G网卡驱动模块,核心业务逻辑为:
1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
]]
local function ip_ready_func(ip, adapter)
if adapter == socket.LWIP_GP then
-- 设置DNS服务器IP地址
socket.setDNS(adapter, 1, "223.5.5.5")
socket.setDNS(adapter, 2, "114.114.114.114")
log.info("netdrv_4g", "IP_READY", socket.localIP(socket.LWIP_GP))
end
end
local function ip_lose_func(adapter)
if adapter == socket.LWIP_GP then
log.warn("netdrv_4g", "IP_LOSE")
end
end
-- 订阅"IP_READY"和"IP_LOSE"两种消息
sys.subscribe("IP_READY", ip_ready_func)
sys.subscribe("IP_LOSE", ip_lose_func)
-- 在Air780EPM/Air8780P上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
@@ -0,0 +1,237 @@
--[[
@module prj_vl53l1x
@summary Air8780P VL53L1X激光测距传感器低功耗数据采集与上报项目编排模块
@version 2.0
@date 2026.07.23
@author 江访
@usage
本文件为项目主业务编排模块,负责协调各功能模块完成完整业务流程。
三层架构说明:
drv/ 硬件驱动层 — 通过 sys.subscribe 注册,事件驱动
drv_led → LED_SET_HIGH / LED_SET_LOW
drv_psm → DRV_SET_PSM
app/ 应用功能层 — return table,直接调用
sensor_vl53l1x — 传感器驱动封装
aircloud — 云平台数据上报
fota_mgr — FOTA远程升级管理
prj/ 项目编排层 — 本文件,协调业务流程
完整业务流程:
① 发布LED_SET_HIGH → drv_led点亮LED
② 检查FOTA首次启动(fota_mgr.check_first_boot
③ 初始化传感器(sensor_vl53l1x.init
④ 采集传感器数据(sensor_vl53l1x.collect_data
⑤ 关闭传感器(sensor_vl53l1x.close
⑥ 等待4G网络就绪
⑦ 初始化excloud连接(aircloud.init
⑧ 双通道上报(aircloud.report_excloud + report_mqtt
⑨ FOTA升级检查(fota_mgr.check_and_upgrade
⑩ 关闭传感器 + 发布LED_SET_LOW → drv_led熄灭LED
⑪ 发布DRV_SET_PSM → drv_psm进入PSM+
消息协议(发布/订阅):
发布: LED_SET_HIGH(gpio) → drv_led: 拉高LED
发布: LED_SET_LOW(gpio) → drv_led: 拉低LED
发布: DRV_SET_PSM(sleep_min) → drv_psm: 进入PSM+模式
本文件没有对外接口,require "prj_vl53l1x"即可加载运行
]]
-- ==================== 加载应用功能模块(app层,return table直接调用) ====================
-- sensor_vl53l1x: VL53L1X传感器驱动封装,提供init/collect_data/close接口
local sensor_vl53l1x = require "sensor_vl53l1x"
-- aircloud: 云平台数据上报,封装excloud和MQTT双通道上报
local aircloud = require "aircloud"
-- fota_mgr: FOTA远程升级管理,含版本文件管理和首次启动检测
local fota_mgr = require "fota_mgr"
-- ==================== 加载硬件驱动模块(drv层,事件订阅,不返回接口表) ====================
-- drv_led: LED指示灯控制,订阅LED_SET_HIGH/LED_SET_LOW消息
-- 通过sys.publish("LED_SET_HIGH", gpio)控制LED点亮
-- 通过sys.publish("LED_SET_LOW", gpio)控制LED熄灭
require "drv_led"
-- drv_psm: PSM+低功耗模式驱动,订阅DRV_SET_PSM消息
-- 通过sys.publish("DRV_SET_PSM", sleep_min)进入PSM+模式
-- 进入前需要先关闭外设和熄灭LED
require "drv_psm"
-- ==================== 可配置参数 ====================
-- 用户可根据项目需求修改以下参数
-- 这些参数决定了设备的工作行为和功耗表现
local CFG = {
-- PSM+休眠时间(分钟)
-- 唤醒后重新开始完整的数据采集和上报循环
-- 合宙IoT平台建议最多10分钟请求一次,建议≤10
psm_sleep_min_s = 15,
-- 开机到进入PSM+的超时上限(秒)
-- 到时间后不管任务2是否完成,都强制进入PSM+
psm_entry_max_s = 90,
-- FOTA等待最长时间(秒)
-- 超时说明网络不好,进入PSM+下次再试
fota_wait_max_s = 600,
-- 每次唤醒后采集传感器的次数
-- 多次采集取平均值可以降低单次测量误差
sensor_samples = 3,
-- 测距模式
-- "short" — 短距离(~1.36m),抗环境光能力强
-- "standard" — 标准距离(~2.9m),通用场景
-- "long" — 长距离(~4.6m),需较好的环境光条件
sensor_range_mode = "standard",
-- I2C引脚定义(软件I2C模式)
gpio_i2c_scl = 1, -- I2C SCLGPIO1
gpio_i2c_sda = 2, -- I2C SDAGPIO2
gpio_led_ctrl = 27, -- LED指示灯,高电平点亮,PSM+前熄灭
-- MQTT配置(使用合宙测试服务器)
mqtt_broker = "lbsmqtt.airm2m.com",
mqtt_port = 1884,
}
-- ==================== 网络等待工具函数 ====================
--[[
等待4G网络就绪
通过socket.adapter判断默认网卡是否已获得IP地址。
每秒检查一次,同时等待IP_READY消息,兼顾即时性和兜底。
@param number timeout_s 等待超时时间(秒),默认120秒
@return boolean true=网络就绪,false=超时未就绪
]]
local function wait_network_ready(timeout_s)
timeout_s = timeout_s or 120
log.info("prj_vl53l1x", "等待4G网络就绪,最长" .. timeout_s .. "秒...")
for i = 1, timeout_s do
if socket.adapter(socket.dft()) then
log.info("prj_vl53l1x", "4G网络已就绪,IP:", socket.localIP(socket.LWIP_GP))
return true
end
log.warn("prj_vl53l1x", "等待IP_READY", socket.dft())
sys.waitUntil("IP_READY", 1000)
end
log.warn("prj_vl53l1x", "4G网络等待超时(未插卡或无信号),跳过联网操作")
return false
end
-- ==================== 项目主任务 ====================
local function vl53l1x_task()
log.info("prj_vl53l1x", "========== Air8780P VL53L1X 主任务启动 ==========")
-- ==================== 第一步:LED点亮 ====================
-- 通过事件驱动drv_led模块控制GPIO
-- drv_led接收到LED_SET_HIGH消息后,将指定GPIO拉高
sys.publish("LED_SET_HIGH", CFG.gpio_led_ctrl)
-- ==================== 第二步:检查FOTA首次启动 ====================
-- 比对版本标记文件中的版本号与当前运行版本号
-- 如果版本不一致,说明经历了FOTA升级并重启
-- 返回的字符串包含升级前后版本信息,需在上报时附带
local fota_first_boot_info = fota_mgr.check_first_boot()
if fota_first_boot_info then
log.info("prj_vl53l1x", "FOTA升级后首次启动:", fota_first_boot_info)
end
-- ==================== 第三步:初始化传感器 ====================
-- 配置I2C引脚并初始化VL53L1X传感器
-- 如果初始化失败,跳过数据采集步骤
local sensor_ok = sensor_vl53l1x.init(CFG.gpio_i2c_scl, CFG.gpio_i2c_sda, CFG.sensor_range_mode)
-- ==================== 第四步:采集传感器数据 ====================
-- 采集多次后取平均值,降低单次测量误差
local sensor_data = nil
if sensor_ok then
sensor_data = sensor_vl53l1x.collect_data(CFG.sensor_samples)
if sensor_data then
log.info("prj_vl53l1x", string.format("测距结果 = %d mm (有效%d帧)",
sensor_data.avg_distance, sensor_data.valid_count))
else
log.warn("prj_vl53l1x", "测距结果 = 无效(所有帧均未测到目标)")
end
-- 采集完成后立即关闭传感器,降低功耗
sensor_vl53l1x.close()
end
-- ==================== 第五步:等待4G网络就绪 ====================
-- 如果超时,跳过所有需要联网的操作
local net_ok = wait_network_ready()
if net_ok then
-- ==================== 第六步:初始化excloud连接 ====================
-- 连接到合宙IoT平台,等待认证完成
aircloud.init({transport = "tcp", auto_reconnect = true, reconnect_interval = 10, max_reconnect = 3})
-- ==================== 第七步:双通道数据上报 ====================
-- 通过excloud(合宙IoT平台)和MQTT双通道同时上报
local mqtt_topic = mobile.imei() .. "/vl53l1x/up"
local mqtt_payload
if fota_first_boot_info then
-- FOTA升级后首次启动:上报升级信息+传感器数据
local fota_msg = "FOTA升级成功: " .. fota_first_boot_info
aircloud.report_excloud(sensor_data, fota_msg)
mqtt_payload = aircloud.build_mqtt_payload(sensor_data, fota_msg)
-- 上报完成后删除版本文件,避免下次重启再次上报
fota_mgr.clear_version_file()
else
-- 正常启动:仅上报传感器数据
aircloud.report_excloud(sensor_data, nil)
mqtt_payload = aircloud.build_mqtt_payload(sensor_data, nil)
end
aircloud.report_mqtt(mqtt_topic, mqtt_payload, CFG.mqtt_broker, CFG.mqtt_port)
-- ==================== 第八步:FOTA升级检查 ====================
-- 非FOTA后首次启动才检查FOTA升级
if not fota_first_boot_info then
aircloud.report_fota_status("FOTA: 正在检查/升级中", sensor_data)
local need_reboot = fota_mgr.check_and_upgrade(CFG.fota_wait_max_s)
if need_reboot then
aircloud.report_excloud(nil, "FOTA升级包下载成功, 升级前版本: " .. VERSION)
sys.wait(1000)
rtos.reboot()
end
end
else
-- 无网络:跳过所有联网操作,直接进入PSM+
if fota_first_boot_info then
fota_mgr.clear_version_file()
end
log.info("prj_vl53l1x", "无网络,跳过上报和FOTA检查")
end
-- ==================== 第九步:关闭外设并进入PSM+ ====================
-- 关闭传感器
sensor_vl53l1x.close()
-- 熄灭LED(通过事件驱动drv_led)
-- drv_led接收到LED_SET_LOW消息后,将指定GPIO拉低
sys.publish("LED_SET_LOW", CFG.gpio_led_ctrl)
log.info("prj_vl53l1x", "所有业务完成,准备进入PSM+模式,休眠" .. CFG.psm_sleep_min_s .. "分钟")
-- 发布PSM+进入消息,通知drv_psm模块
-- drv_psm接收到DRV_SET_PSM消息后,在独立协程中执行:
-- 1. 设置深度休眠定时器(dtimer)
-- 2. 调用pm.power(pm.WORK_MODE, 3)
-- 3. 等待80秒后如果未进入PSM+则强制重启
-- drv_psm模块内部已有中断唤醒引脚和功能引脚的配置说明
sys.publish("DRV_SET_PSM", CFG.psm_sleep_min_s)
end
-- ==================== 启动项目主任务 ====================
sys.taskInit(vl53l1x_task)
@@ -0,0 +1,517 @@
# Air8780P + VL53L1X 激光测距传感器低功耗数据采集与上报 Demo
## 一、功能模块介绍
### 1.1 核心主程序模块
1. **main.lua** - 主程序入口,负责 PRODUCT_KEY 定义、errDump 开启、网络驱动加载和业务模块调度
2. **app_vl53l1x_main.lua** - 业务逻辑主协调模块,实现双任务架构(PSM+超时管理 + 业务逻辑),调用各模块完成完整流程
3. **sensor_vl53l1x.lua** - VL53L1X 传感器驱动封装模块,含初始化、数据采集、关闭接口
4. **aircloud.lua** - 云平台数据上报模块,封装 excloud 和 MQTT 双通道上报,含 payload 构建
5. **fota_mgr.lua** - FOTA 远程升级管理模块,含版本文件管理、首次启动检测、升级检查
6. **psm_mgr.lua** - PSM+ 低功耗模式管理模块,含 LED 指示控制和深度休眠入口
7. **netdrv_device.lua** - 网络驱动设备模块
8. **netdrv/netdrv_4g.lua** - 4G 网卡驱动
### 1.2 扩展库模块
1. **exs_vl53l1x** - VL53L1X 激光测距传感器扩展库
2. **libfota2** - 远程升级扩展库
3. **excloud** - 合宙 IoT 平台数据交互扩展库
## 二、演示流程介绍
本 demo 采用双任务架构:
- **任务1(PSM+超时管理)**:到设定时间就进入 PSM+,期间等待 FOTA 消息,收到后等待升级结果(最长 10 分钟),超时或失败也进入 PSM+ 下次再试
- **任务2(业务逻辑)**:传感器采集 → 联网 → 双通道上报 → FOTA 检查 → PSM+
开机 → GPIO27 拉高(LED亮) → 检查FOTA后首次启动 → 传感器初始化 → 采集3次测距数据 → 关闭传感器 → 等待4G网络 → excloud连接 → 双通道(aircloud+MQTT)上报 → FOTA检查 → 进入PSM+模式休眠 → 定时唤醒后重复
## 三、硬件准备
### 3.1 硬件清单
- Air8780P 工业模组 × 1
- VL53L1X 激光测距传感器模块 × 1
- SIM 卡 × 14G 联网)
- 母对母杜邦线 × 4
- TYPE-C 数据线 × 1
### 3.2 接线配置
#### 3.2.1 VL53L1X 传感器接线
| Air8780P | VL53L1X |
|----------|---------|
| 3.3V | VIN |
| GND | GND |
| GPIO1 | SCL |
| GPIO2 | SDA |
> 说明:VL53L1X 传感器 I2C 地址默认 0x29,支持软件 I2C 和硬件 I2C 两种模式,本 demo 使用 GPIO1/GPIO2 软件 I2C 模式。
![](https://docs.openluat.com/cdn/image/Air8780P_vl53l1x.png)
#### 3.2.2 指示灯说明
| Air8780P | 功能 |
|----------|------|
| GPIO27 | LED 指示灯,正常工作状态拉高点亮,进入 PSM+ 前拉低熄灭 |
## 四、演示软件环境
### 4.1 开发工具
- [Luatools下载调试工具](https://docs.openluat.com/air780egh/luatos/common/download/) - 固件烧录和代码调试
### 4.2 内核固件
- [点击下载Air780EPM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/780epm_version/)demo所使用的是 LuatOS-SoC_V2046_Air780EPM 1号固件
### 4.3 脚本文件
1. **main.lua** - 主程序入口
2. **prj/prj_vl53l1x.lua** - 项目编排模块(业务协调/事件驱动)
3. **drv/netdrv_device.lua** - 网络驱动设备模块
4. **drv/drv_led.lua** - LED指示灯驱动(事件订阅:LED_SET_HIGH/LED_SET_LOW
5. **drv/drv_psm.lua** - PSM+低功耗模式驱动(事件订阅:DRV_SET_PSM
6. **sensor/sensor_vl53l1x.lua** - VL53L1X 传感器驱动封装模块
7. **cloud/aircloud.lua** - 云平台(excloud + MQTT)数据上报模块
8. **fota/fota_mgr.lua** - FOTA 远程升级管理模块
9. **netdrv/netdrv_4g.lua** - 4G 网卡驱动
9. **exs_vl53l1x** (扩展库) - VL53L1X 测距传感器驱动
10. **libfota2** (扩展库) - 远程升级
11. **excloud** (扩展库) - 合宙 IoT 平台
## 五、演示核心步骤
### 5.1 硬件准备
1. 按照接线表将 VL53L1X 传感器模块连接到 Air8780P 模组
2. 确认 SIM 卡已正确安装
3. 通过 TYPE-C USB 口供电
4. 检查所有接线无误,避免短路
### 5.2 软件配置
`main.lua` 中配置 PRODUCT_KEY
```lua
PRODUCT_KEY = "PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ" -- 请改为实际的PRODUCT_KEY
```
`app_vl53l1x_main.lua` 中可根据需求调整以下参数:
```lua
local CFG = {
psm_entry_max_s = 90, -- 开机到进入PSM+的超时上限(秒)
psm_sleep_min_s = 15, -- PSM+休眠时间(分钟),唤醒后重新开始循环
fota_wait_max_s = 600, -- FOTA等待最长时间(秒),默认10分钟
sensor_samples = 3, -- 每次唤醒后采集传感器的次数
}
```
如需更换 MQTT 服务器,修改 `app_vl53l1x_main.lua` 中 CFG 的 `mqtt_broker``mqtt_port`
### 5.3 软件烧录
1. 使用 Luatools 选择最新内核固件
2. 下载本项目所有脚本文件
3. 将固件和脚本一起烧录到设备
4. 烧录成功后设备自动重启后开始运行
### 5.4 功能流程说明
#### 5.4.1 双任务架构
本 demo 同时运行两个任务:
**任务1PSM+超时管理)**:开机后立即启动计时,最大等待 `psm_entry_max_s` 秒。期间等待 FOTA 消息:
- 未收到 FOTA 消息 → 到时间直接进入 PSM+
- 收到 `FOTA_UPGRADING` → 延长等待 FOTA 结果,最长 `fota_wait_max_s` 秒(默认 10 分钟)
- FOTA 成功 → 上报升级结果后重启
- FOTA 失败或超时 → 进入 PSM+,下次唤醒再试
**任务2(业务逻辑)**:完成传感器采集、联网上报、FOTA 检查后进入 PSM+
> 两个任务任意一个先到 PSM+ 入口时,系统在所有任务都阻塞后自动进入深度休眠。
#### 5.4.2 开机自检
1. 设备启动后,GPIO27 拉高(LED 点亮),表示进入正常工作状态
2. 检查版本标记文件:如果检测到 FOTA 升级后的首次启动,会在日志中打印升级前后版本信息
3. 初始化 VL53L1X 传感器,等待固件就绪、写入预设模式配置
#### 5.4.2 数据采集
1. 传感器初始化成功后,开始采集测距数据,默认采集 3 次
2. 每次采集间隔 500ms,日志中会打印每次采集的距离值和状态
3. 计算有效数据的平均值作为本次上报的测距结果
#### 5.4.3 数据上报
1. 等待 4G 网络就绪
2. 连接合宙 IoT 平台(aircloud
3. 通过 aircloud 和 MQTT 双通道上报传感器数据:
- aircloud:上报平均距离
- MQTT:上报 JSON 格式完整数据,Topic 为 `IMEI/vl53l1x/up`
#### 5.4.4 FOTA 升级
1. 非 FOTA 后首次启动时,任务2检查 libfota2 远程升级,同时发布 `FOTA_UPGRADING` 消息通知任务1
2. 任务1收到消息后延长 PSM+ 等待时间,最长 `fota_wait_max_s` 秒(默认 10 分钟)
3. 任务2上报"FOTA: 正在检查/升级中"状态
4. 升级包下载成功后上报升级结果,然后重启模块
5. 重启后首次运行会上报升级前后版本对比信息,上报完成后删除版本标记文件
6. FOTA 超时(如网络不好)→ 进入 PSM+,下次唤醒再试
#### 5.4.5 PSM+ 低功耗
1. 所有业务处理完成后,配置深度休眠定时器
2. GPIO27 拉低(LED 熄灭)
3. 调用 `pm.power(pm.WORK_MODE, 3)` 进入 PSM+ 模式
4. 定时唤醒后系统重新启动,从 main.lua 开始执行
### 5.5 预期效果
- **数据采集**:每次开机自动采集 3 次距离数据,日志实时打印
- **联网上报**aircloud 和 MQTT 双通道同时上报,数据格式完整
- **低功耗**:上报完成后进入 PSM+ 模式,功耗降至 uA 级别
- **定时唤醒**:默认 15 分钟(可配置)后唤醒,重复整个流程
- **日志如下:**
```
[2026-07-21 16:50:57.264][000000000.000] main_entry 708:SDK base line V017_p001.026
[2026-07-21 16:50:57.267][000000000.007] am_service_init 1372:Air780EPM_A11
[2026-07-21 16:50:57.268][000000000.007] am_get_chip_type 868:6bef6,8,4,31,8,EC718PM
[2026-07-21 16:50:57.270][000000000.007] am_service_init 1380:APB MP 102400000
[2026-07-21 16:50:57.273][000000000.048] bsp_user_init_io 417:io volt 3.3v 21
[2026-07-21 16:50:57.276][000000000.048] BSP_CustomInit 558:hardfault mode init 4
[2026-07-21 16:50:57.278][000000000.049] Uart_ChangeBR 1461:uart0, 6000000 6028985 26000000 69
[2026-07-21 16:50:57.284][000000000.070] I/pm poweron: Power/Reset
[2026-07-21 16:50:57.287][000000000.070] luat_pm_get_poweron_reason 336:ap 1, cp 1
[2026-07-21 16:50:57.290][000000000.070] I/pm poweron reason: 0 0 0
[2026-07-21 16:50:57.293][000000000.178] self_info 125:model Air780EPM_A11 imei 864317083866528 dbversion 0x405e3b06
[2026-07-21 16:50:57.295][000000000.178] self_info 127:firmware[1] BASIC fs 168kbyte script 256kbyte
[2026-07-21 16:50:57.300][000000000.178] I/main LuatOS@Air780EPM base 26.04 bsp V2046 32bit
[2026-07-21 16:50:57.304][000000000.178] I/main ROM Build: Jul 7 2026 20:39:17
[2026-07-21 16:50:57.307][000000000.180] W/pins /luadb/pins_air780epm.json not exist!!
[2026-07-21 16:50:57.311][000000000.182] D/main loadlibs luavm 1048568 14784 14784
[2026-07-21 16:50:57.315][000000000.183] D/main loadlibs sys 2366664 82956 89556
[2026-07-21 16:50:57.318][000000000.183] D/main loadlibs psram 2366664 82956 89556
[2026-07-21 16:50:57.320][000000000.200] I/user.main Air8780P_VL53L1X 001.999.000
[2026-07-21 16:50:57.322][000000000.236] D/user.libfota2 version -> 202607021200
[2026-07-21 16:50:57.327][000000000.272] D/user.httpplus version -> 202607021200
[2026-07-21 16:50:57.330][000000000.284] D/user.exmtn version -> 202607021200
[2026-07-21 16:50:57.332][000000000.289] D/user.excloud version -> 202607091431
[2026-07-21 16:50:57.334][000000000.293] I/user.app_vl53l1x ========== Air8780P VL53L1X 主任务启动 ==========
[2026-07-21 16:50:57.337][000000000.294] I/user.app_vl53l1x 配置: wait=60s psm=5min samples=3
[2026-07-21 16:50:57.343][000000000.294] I/user.app_vl53l1x GPIO27 HIGH
[2026-07-21 16:50:57.347][000000000.301] I/user.app_vl53l1x 保存版本信息到文件: 001.999.000,Air8780P_VL53L1X
[2026-07-21 16:50:57.373][000000000.302] I/user.app_vl53l1x 正在初始化VL53L1X传感器...
[2026-07-21 16:50:57.384][000000000.318] I/user.exs_vl53l1x 固件就绪 0x00E5=0x03
[2026-07-21 16:50:57.394][000000000.436] I/user.exs_vl53l1x 预设模式配置写入完成 (mode=standard)
[2026-07-21 16:50:57.402][000000000.440] I/user.exs_vl53l1x 芯片 MID=0xEA MT=0xCC
[2026-07-21 16:50:57.418][000000000.642] I/user.exs_vl53l1x 初始化完成,Standard Ranging 测距已启动
[2026-07-21 16:50:57.424][000000000.643] I/user.app_vl53l1x VL53L1X初始化成功
[2026-07-21 16:50:57.429][000000000.643] I/user.app_vl53l1x 开始采集传感器数据,采集次数=3
[2026-07-21 16:50:57.878][000000001.195] I/user.app_vl53l1x 采集[1/3] 距离=267mm 状态=测距成功
[2026-07-21 16:50:58.147][000000001.704] I/user.app_vl53l1x 采集[2/3] 距离=261mm 状态=测距成功
[2026-07-21 16:50:58.158][000000002.215] I/user.app_vl53l1x 采集[3/3] 距离=267mm 状态=测距成功
[2026-07-21 16:50:58.166][000000002.215] I/user.app_vl53l1x 采集完成: 有效3帧, 平均距离=265mm
[2026-07-21 16:50:58.177][000000002.216] I/user.app_vl53l1x 测距结果 = 265 mm (有效3帧, 原始数据=267,261,267)
[2026-07-21 16:50:58.188][000000002.216] I/user.app_vl53l1x 关闭VL53L1X传感器
[2026-07-21 16:50:58.196][000000002.222] I/user.exs_vl53l1x 传感器已关闭
[2026-07-21 16:50:58.208][000000002.222] I/user.app_vl53l1x 等待4G网络就绪...
[2026-07-21 16:50:58.214][000000002.222] W/user.app_vl53l1x 等待IP_READY 1 1
[2026-07-21 16:50:58.465][000000002.250] I/mobile sim0 sms ready
[2026-07-21 16:50:58.474][000000002.251] D/mobile cid1, state0
[2026-07-21 16:50:58.483][000000002.252] D/mobile bearer act 0, result 0
[2026-07-21 16:50:58.493][000000002.252] D/mobile NETIF_LINK_ON -> IP_READY
[2026-07-21 16:50:58.503][000000002.253] I/user.netdrv_4g IP_READY 10.51.56.19 255.255.255.255 0.0.0.0 nil
[2026-07-21 16:50:58.522][000000002.254] I/user.app_vl53l1x 4G网络已就绪 10.51.56.19 255.255.255.255 0.0.0.0 nil
[2026-07-21 16:50:58.538][000000002.255] I/user.app_vl53l1x 正在初始化excloud...
[2026-07-21 16:50:58.549][000000002.255] W/user.excloud.setup 不再需要主动配置use_getip
[2026-07-21 16:50:58.562][000000002.255] W/user.excloud.setup 不再需要主动配置device_type
[2026-07-21 16:50:58.582][000000002.257] I/mobile l_mobile_muid called
[2026-07-21 16:50:58.587][000000002.263] I/mobile l_mobile_muid ret=32
[2026-07-21 16:50:58.600][000000002.263] I/user.[excloud]4G设备 IMEI: 864317083866528 MUID: 20260702152525A860510A9885483051
[2026-07-21 16:50:58.613][000000002.265] I/user.[excloud]setup 初始化成功 设备ID: 864317083866528
[2026-07-21 16:50:58.626][000000002.265] I/user.[excloud]首次连接,获取服务器信息...
[2026-07-21 16:50:58.635][000000002.266] I/mobile l_mobile_muid called
[2026-07-21 16:50:58.652][000000002.267] I/mobile l_mobile_muid ret=32
[2026-07-21 16:50:58.670][000000002.268] I/user.[excloud]getip 类型: 3 key: unusedkey-864317083866528-20260702152525A860510A9885483051
[2026-07-21 16:50:58.680][000000002.278] D/socket connect to gps.openluat.com,443
[2026-07-21 16:50:58.694][000000002.279] dns_run 676:gps.openluat.com state 0 id 1 ipv6 0 use dns server0, try 0
[2026-07-21 16:50:58.707][000000002.297] D/mobile TIME_SYNC 0 tm 1784623860
[2026-07-21 16:50:58.716][000000002.321] dns_run 693:dns all done ,now stop
[2026-07-21 16:50:58.936][000000003.217] I/user.httpplus 服务器已完成响应
[2026-07-21 16:50:58.945][000000003.219] I/user.[excloud]getip响应 HTTP: 200 Body:
[2026-07-21 16:50:58.950][000000003.220] {"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108,"auth_key":"PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ"},"imginfo":{"url":"https://api.luatos.com/iot/air_up/image","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}},"audinfo":{"url":"https://api.luatos.com/iot/air_up/audio","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}},"mtninfo":{"url":"https://api.luatos.com/iot/air_up/file","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}}}
[2026-07-21 16:50:58.968][000000003.221] I/user.[excloud]TCP/UDP连接信息 host: 124.71.128.165 port: 9108 key: nil
[2026-07-21 16:50:58.973][000000003.221] I/user.[excloud]获取到图片上传信息
[2026-07-21 16:50:58.979][000000003.222] I/user.[excloud]获取到音频上传信息
[2026-07-21 16:50:58.994][000000003.222] I/user.[excloud]获取到运维日志上传信息
[2026-07-21 16:50:59.005][000000003.222] W/user.[excloud]未获取到二维码信息
[2026-07-21 16:50:59.012][000000003.223] I/user.[excloud]自动获取到auth_key
[2026-07-21 16:50:59.022][000000003.223] I/user.[excloud]getip 更新配置: 124.71.128.165 9108
[2026-07-21 16:50:59.029][000000003.223] I/user.[excloud]getip 成功: true
[2026-07-21 16:50:59.038][000000003.224] I/user.[excloud]服务器信息获取成功 host: 124.71.128.165 port: 9108 transport: tcp
[2026-07-21 16:50:59.043][000000003.224] I/user.[excloud]创建TCP连接
[2026-07-21 16:50:59.056][000000003.225] D/socket connect to 124.71.128.165,9108
[2026-07-21 16:50:59.067][000000003.227] I/user.[excloud]TCP连接结果 true false
[2026-07-21 16:50:59.071][000000003.227] I/user.[excloud]excloud service started
[2026-07-21 16:50:59.081][000000003.228] I/user.[excloud]excloud 自动心跳已启动,间隔 300 秒
[2026-07-21 16:50:59.088][000000003.282] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554449 0
[2026-07-21 16:50:59.093][000000003.282] I/user.[excloud]TCP socket TCP连接成功
[2026-07-21 16:50:59.103][000000003.283] I/user.app_vl53l1x excloud连接成功
[2026-07-21 16:50:59.111][000000003.283] I/mobile l_mobile_muid called
[2026-07-21 16:50:59.117][000000003.285] I/mobile l_mobile_muid ret=32
[2026-07-21 16:50:59.120][000000003.285] I/user.[excloud] 发送鉴权请求
[2026-07-21 16:50:59.126][000000003.286] I/user.[excloud]构建发送数据 field: 16 type: 3 value: PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ-864317083866528-20260702152525A860510A9885483051
[2026-07-21 16:50:59.134][000000003.287] I/user.[excloud]tlv发送数据长度4 85
[2026-07-21 16:50:59.137][000000003.287] I/user.[excloud]构建消息头 seq: 2 len: 85 flags: 18 dev: 0186431708386652
[2026-07-21 16:50:59.147][000000003.290] I/user.app_vl53l1x excloud发送成功, 流水号: 2
[2026-07-21 16:50:59.151][000000003.290] I/user.[excloud]数据发送成功 101 字节
[2026-07-21 16:50:59.155][000000003.351] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554450 0
[2026-07-21 16:50:59.161][000000003.352] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:50:59.165][000000003.411] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554452 0
[2026-07-21 16:50:59.171][000000003.412] I/user.[excloud]TCP socket 收到数据 22 字节 01864317083866520001000600000000301100026F6B 44
[2026-07-21 16:50:59.181][000000003.413] I/user.[excloud]解析消息头 设备ID: 864317083866528 序列号: 1 消息长度: 6 协议版本: 0 需要回复: false UDP承载: false 包含auth_key: false
[2026-07-21 16:50:59.187][000000003.414] I/user.[excloud]鉴权成功 ok
[2026-07-21 16:50:59.193][000000003.415] I/user.app_vl53l1x excloud认证成功
[2026-07-21 16:50:59.198][000000003.417] I/user.[excloud]构建发送数据 field: 20 type: 5 value: 265
[2026-07-21 16:50:59.204][000000003.418] I/user.[excloud]tlv发送数据长度4 7
[2026-07-21 16:50:59.207][000000003.419] I/user.[excloud]构建消息头 seq: 3 len: 7 flags: 2 dev: 0186431708386652
[2026-07-21 16:50:59.211][000000003.421] I/user.app_vl53l1x excloud发送成功, 流水号: 3
[2026-07-21 16:50:59.215][000000003.421] I/user.[excloud]数据发送成功 23 字节
[2026-07-21 16:50:59.222][000000003.422] I/user.app_vl53l1x excloud数据上报成功
[2026-07-21 16:50:59.227][000000003.424] I/user.app_vl53l1x MQTT上报数据到: 864317083866528/vl53l1x/up
[2026-07-21 16:50:59.231][000000003.428] dns_run 676:lbsmqtt.airm2m.com state 0 id 2 ipv6 0 use dns server0, try 0
[2026-07-21 16:50:59.238][000000003.461] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554450 0
[2026-07-21 16:50:59.243][000000003.462] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:50:59.247][000000003.471] dns_run 693:dns all done ,now stop
[2026-07-21 16:50:59.313][000000003.599] I/user.app_vl53l1x MQTT事件: conack nil
[2026-07-21 16:50:59.322][000000003.599] I/user.app_vl53l1x MQTT连接成功
[2026-07-21 16:50:59.328][000000003.601] I/user.app_vl53l1x MQTT已发布: {"distance_mm":265,"project":"Air8780P_VL53L1X","distances":[267,261,267],"imei":"864317083866528","valid_samples":3,"ts":1784623861,"version":"001.999.000"}
[2026-07-21 16:50:59.391][000000003.679] I/user.app_vl53l1x MQTT事件: sent 1
[2026-07-21 16:50:59.399][000000003.679] I/user.app_vl53l1x MQTT数据发送成功
[2026-07-21 16:50:59.405][000000003.681] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:3
[2026-07-21 16:50:59.414][000000003.682] I/user.app_vl53l1x MQTT事件: disconnect 0
[2026-07-21 16:50:59.418][000000003.682] W/user.app_vl53l1x MQTT断开
[2026-07-21 16:50:59.429][000000003.683] I/user.app_vl53l1x MQTT事件: close nil
[2026-07-21 16:50:59.899][000000004.181] W/mqtt network_tx ret -1, closing socket
[2026-07-21 16:50:59.903][000000004.182] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:0
[2026-07-21 16:50:59.907][000000004.182] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:0
[2026-07-21 16:50:59.912][000000004.183] I/user.app_vl53l1x MQTT短连接结束,发送 成功
[2026-07-21 16:50:59.919][000000004.183] I/user.app_vl53l1x 开始检查FOTA升级...
[2026-07-21 16:50:59.924][000000004.186] I/user.app_vl53l1x 保存版本信息到文件: 001.999.000,Air8780P_VL53L1X
[2026-07-21 16:50:59.929][000000004.186] I/user.[excloud]构建发送数据 field: 20 type: 5 value: FOTA: 正在检查/升级中
[2026-07-21 16:50:59.940][000000004.188] I/user.[excloud]tlv发送数据长度4 32
[2026-07-21 16:50:59.944][000000004.188] I/user.[excloud]构建消息头 seq: 4 len: 32 flags: 2 dev: 0186431708386652
[2026-07-21 16:50:59.949][000000004.190] I/user.app_vl53l1x excloud发送成功, 流水号: 4
[2026-07-21 16:50:59.953][000000004.191] I/user.[excloud]数据发送成功 48 字节
[2026-07-21 16:50:59.957][000000004.191] I/user.app_vl53l1x 已上报FOTA升级状态
[2026-07-21 16:50:59.962][000000004.192] I/user.app_vl53l1x FOTA期间同时上报传感器数据: 265 mm
[2026-07-21 16:50:59.970][000000004.194] I/user.libfota2.url GET http://iot.openluat.com/api/site/firmware_upgrade?imei=864317083866528&project_key=PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ&firmware_name=Air8780P_VL53L1X_LuatOS-SoC_Air780EPM&version=2046.001.000
[2026-07-21 16:50:59.974][000000004.194] I/user.libfota2.imei/mac imei=864317083866528
[2026-07-21 16:50:59.978][000000004.195] I/user.libfota2.project_key PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ
[2026-07-21 16:50:59.982][000000004.195] I/user.libfota2.firmware_name Air8780P_VL53L1X_LuatOS-SoC_Air780EPM
[2026-07-21 16:50:59.985][000000004.195] I/user.libfota2.version 2046.001.000
[2026-07-21 16:50:59.988][000000004.197] dns_run 676:iot.openluat.com state 0 id 3 ipv6 0 use dns server0, try 0
[2026-07-21 16:50:59.995][000000004.241] dns_run 693:dns all done ,now stop
[2026-07-21 16:51:00.003][000000004.243] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554450 0
[2026-07-21 16:51:00.006][000000004.243] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:51:00.149][000000004.393] I/fota write common data
[2026-07-21 16:51:00.573][000000004.809] I/fota common data done, now checking 0
[2026-07-21 16:51:00.583][000000004.815] I/fota common data md5 ok
[2026-07-21 16:51:00.586][000000004.815] I/fota only common data
[2026-07-21 16:51:00.590][000000004.815] I/fota 脚本地址匹配
[2026-07-21 16:51:00.594][000000004.849] I/fota fota type 0 ok!, wait reboot
[2026-07-21 16:51:00.599][000000004.852] I/http http close c1d7174
[2026-07-21 16:51:00.604][000000004.853] I/user.app_vl53l1x FOTA回调 ret= 0
[2026-07-21 16:51:00.611][000000004.853] I/user.app_vl53l1x FOTA升级包下载成功,准备重启
[2026-07-21 16:51:00.615][000000004.854] I/user.app_vl53l1x FOTA升级包已下载,重启模块
[2026-07-21 16:51:00.619][000000004.855] I/user.[excloud]构建发送数据 field: 20 type: 5 value: FOTA升级包下载成功, 升级前版本: 001.999.000
[2026-07-21 16:51:00.623][000000004.856] I/user.[excloud]tlv发送数据长度4 59
[2026-07-21 16:51:00.627][000000004.856] I/user.[excloud]构建消息头 seq: 5 len: 59 flags: 2 dev: 0186431708386652
[2026-07-21 16:51:00.633][000000004.859] I/user.app_vl53l1x excloud发送成功, 流水号: 5
[2026-07-21 16:51:00.646][000000004.859] I/user.[excloud]数据发送成功 75 字节
[2026-07-21 16:51:00.650][000000004.859] I/user.app_vl53l1x excloud数据上报成功
[2026-07-21 16:51:00.654][000000004.901] I/user.[excloud]TCP socket cb userdata: 0C185A00 33554450 0
[2026-07-21 16:51:00.657][000000004.902] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:51:01.663] 工具提示: diag com USB 断开连接 COM12 CommError,[WinError 22] 设备不识别此命令。
[2026-07-21 16:51:03.048] 工具提示: soc log port COM10打开成功
[2026-07-21 16:51:03.147] 工具提示: ap log port COM12打开成功
[2026-07-21 16:51:03.170] 工具提示: 用户虚拟串口 COM11
[2026-07-21 16:51:04.039][000000000.000] main_entry 708:SDK base line V017_p001.026
[2026-07-21 16:51:04.045][000000000.007] am_service_init 1372:Air780EPM_A11
[2026-07-21 16:51:04.050][000000000.007] am_get_chip_type 868:6bef6,8,4,31,8,EC718PM
[2026-07-21 16:51:04.064][000000000.007] am_service_init 1380:APB MP 102400000
[2026-07-21 16:51:04.078][000000000.048] bsp_user_init_io 417:io volt 3.3v 21
[2026-07-21 16:51:04.088][000000000.049] BSP_CustomInit 558:hardfault mode init 4
[2026-07-21 16:51:04.098][000000000.049] Uart_ChangeBR 1461:uart0, 6000000 6028985 26000000 69
[2026-07-21 16:51:04.105][000000000.070] I/pm poweron: Power/Reset
[2026-07-21 16:51:04.110][000000000.071] luat_pm_get_poweron_reason 336:ap 3, cp 2
[2026-07-21 16:51:04.121][000000000.071] I/pm poweron reason: 0 0 3
[2026-07-21 16:51:04.129][000000000.180] self_info 125:model Air780EPM_A11 imei 864317083866528 dbversion 0x405e3b06
[2026-07-21 16:51:04.144][000000000.180] self_info 127:firmware[1] BASIC fs 168kbyte script 256kbyte
[2026-07-21 16:51:04.151][000000000.181] I/main LuatOS@Air780EPM base 26.04 bsp V2046 32bit
[2026-07-21 16:51:04.156][000000000.181] I/main ROM Build: Jul 7 2026 20:39:17
[2026-07-21 16:51:04.161][000000000.183] W/pins /luadb/pins_air780epm.json not exist!!
[2026-07-21 16:51:04.177][000000000.185] D/main loadlibs luavm 1048568 14784 14784
[2026-07-21 16:51:04.184][000000000.185] D/main loadlibs sys 2366664 82956 89556
[2026-07-21 16:51:04.191][000000000.185] D/main loadlibs psram 2366664 82956 89556
[2026-07-21 16:51:04.206][000000000.203] I/user.main Air8780P_VL53L1X 001.999.001
[2026-07-21 16:51:04.216][000000000.241] D/user.libfota2 version -> 202607021200
[2026-07-21 16:51:04.228][000000000.276] D/user.httpplus version -> 202607021200
[2026-07-21 16:51:04.236][000000000.290] D/user.exmtn version -> 202607021200
[2026-07-21 16:51:04.249][000000000.294] D/user.excloud version -> 202607091431
[2026-07-21 16:51:04.272][000000000.298] I/user.app_vl53l1x ========== Air8780P VL53L1X 主任务启动 ==========
[2026-07-21 16:51:04.286][000000000.299] I/user.app_vl53l1x 配置: wait=60s psm=5min samples=3
[2026-07-21 16:51:04.300][000000000.299] I/user.app_vl53l1x GPIO27 HIGH
[2026-07-21 16:51:04.308][000000000.302] I/user.app_vl53l1x 检测到FOTA升级后的首次启动 升级前: PROJECT=Air8780P_VL53L1X VERSION=001.999.000 ; 升级后: PROJECT=Air8780P_VL53L1X VERSION=001.999.001
[2026-07-21 16:51:04.314][000000000.308] I/user.app_vl53l1x 保存版本信息到文件: 001.999.001,Air8780P_VL53L1X
[2026-07-21 16:51:04.321][000000000.309] I/user.app_vl53l1x FOTA升级后首次启动: 升级前: PROJECT=Air8780P_VL53L1X VERSION=001.999.000 ; 升级后: PROJECT=Air8780P_VL53L1X VERSION=001.999.001
[2026-07-21 16:51:04.336][000000000.309] I/user.app_vl53l1x 正在初始化VL53L1X传感器...
[2026-07-21 16:51:04.342][000000000.327] I/user.exs_vl53l1x 固件就绪 0x00E5=0x03
[2026-07-21 16:51:04.347][000000000.446] I/user.exs_vl53l1x 预设模式配置写入完成 (mode=standard)
[2026-07-21 16:51:04.361][000000000.449] I/user.exs_vl53l1x 芯片 MID=0xEA MT=0xCC
[2026-07-21 16:51:04.369][000000000.652] I/user.exs_vl53l1x 初始化完成,Standard Ranging 测距已启动
[2026-07-21 16:51:04.375][000000000.652] I/user.app_vl53l1x VL53L1X初始化成功
[2026-07-21 16:51:04.382][000000000.652] I/user.app_vl53l1x 开始采集传感器数据,采集次数=3
[2026-07-21 16:51:04.868][000000001.205] I/user.app_vl53l1x 采集[1/3] 距离=260mm 状态=测距成功
[2026-07-21 16:51:05.189][000000001.713] I/user.app_vl53l1x 采集[2/3] 距离=258mm 状态=测距成功
[2026-07-21 16:51:05.556][000000002.194] I/mobile sim0 sms ready
[2026-07-21 16:51:05.572][000000002.195] D/mobile cid1, state0
[2026-07-21 16:51:05.585][000000002.196] D/mobile bearer act 0, result 0
[2026-07-21 16:51:05.593][000000002.196] D/mobile NETIF_LINK_ON -> IP_READY
[2026-07-21 16:51:05.614][000000002.197] I/user.netdrv_4g IP_READY 10.32.104.76 255.255.255.255 0.0.0.0 nil
[2026-07-21 16:51:05.619][000000002.227] I/user.app_vl53l1x 采集[3/3] 距离=257mm 状态=测距成功
[2026-07-21 16:51:05.628][000000002.228] I/user.app_vl53l1x 采集完成: 有效3帧, 平均距离=258mm
[2026-07-21 16:51:05.640][000000002.229] I/user.app_vl53l1x 测距结果 = 258 mm (有效3帧, 原始数据=260,258,257)
[2026-07-21 16:51:05.654][000000002.229] I/user.app_vl53l1x 关闭VL53L1X传感器
[2026-07-21 16:51:05.669][000000002.234] I/user.exs_vl53l1x 传感器已关闭
[2026-07-21 16:51:05.682][000000002.234] I/user.app_vl53l1x 等待4G网络就绪...
[2026-07-21 16:51:05.695][000000002.234] I/user.app_vl53l1x 4G网络已就绪 10.32.104.76 255.255.255.255 0.0.0.0 nil
[2026-07-21 16:51:05.712][000000002.235] I/user.app_vl53l1x 正在初始化excloud...
[2026-07-21 16:51:05.724][000000002.235] W/user.excloud.setup 不再需要主动配置use_getip
[2026-07-21 16:51:05.734][000000002.236] W/user.excloud.setup 不再需要主动配置device_type
[2026-07-21 16:51:05.745][000000002.237] I/mobile l_mobile_muid called
[2026-07-21 16:51:05.753][000000002.239] I/mobile l_mobile_muid ret=32
[2026-07-21 16:51:05.768][000000002.239] I/user.[excloud]4G设备 IMEI: 864317083866528 MUID: 20260702152525A860510A9885483051
[2026-07-21 16:51:05.775][000000002.242] I/user.[excloud]setup 初始化成功 设备ID: 864317083866528
[2026-07-21 16:51:05.782][000000002.242] I/user.[excloud]首次连接,获取服务器信息...
[2026-07-21 16:51:05.794][000000002.243] I/mobile l_mobile_muid called
[2026-07-21 16:51:05.801][000000002.244] I/mobile l_mobile_muid ret=32
[2026-07-21 16:51:05.816][000000002.245] I/user.[excloud]getip 类型: 3 key: unusedkey-864317083866528-20260702152525A860510A9885483051
[2026-07-21 16:51:05.822][000000002.252] D/socket connect to gps.openluat.com,443
[2026-07-21 16:51:05.827][000000002.253] dns_run 676:gps.openluat.com state 0 id 1 ipv6 0 use dns server0, try 0
[2026-07-21 16:51:05.836][000000002.254] D/mobile TIME_SYNC 0 tm 1784623867
[2026-07-21 16:51:05.842][000000002.290] dns_run 693:dns all done ,now stop
[2026-07-21 16:51:05.848][000000003.123] I/user.httpplus 服务器已完成响应
[2026-07-21 16:51:05.862][000000003.125] I/user.[excloud]getip响应 HTTP: 200 Body:
[2026-07-21 16:51:05.867][000000003.126] {"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108,"auth_key":"PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ"},"imginfo":{"url":"https://api.luatos.com/iot/air_up/image","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}},"audinfo":{"url":"https://api.luatos.com/iot/air_up/audio","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}},"mtninfo":{"url":"https://api.luatos.com/iot/air_up/file","data_key":"f","data_param":{"key":"7nTSPfh2Pcporb2dNDrS2jforVJaacDWfQbuA","tip":""}}}
[2026-07-21 16:51:05.877][000000003.127] I/user.[excloud]TCP/UDP连接信息 host: 124.71.128.165 port: 9108 key: nil
[2026-07-21 16:51:05.888][000000003.127] I/user.[excloud]获取到图片上传信息
[2026-07-21 16:51:05.898][000000003.128] I/user.[excloud]获取到音频上传信息
[2026-07-21 16:51:05.912][000000003.128] I/user.[excloud]获取到运维日志上传信息
[2026-07-21 16:51:05.917][000000003.129] W/user.[excloud]未获取到二维码信息
[2026-07-21 16:51:05.923][000000003.129] I/user.[excloud]自动获取到auth_key
[2026-07-21 16:51:05.944][000000003.129] I/user.[excloud]getip 更新配置: 124.71.128.165 9108
[2026-07-21 16:51:05.951][000000003.130] I/user.[excloud]getip 成功: true
[2026-07-21 16:51:05.958][000000003.130] I/user.[excloud]服务器信息获取成功 host: 124.71.128.165 port: 9108 transport: tcp
[2026-07-21 16:51:05.977][000000003.131] I/user.[excloud]创建TCP连接
[2026-07-21 16:51:05.981][000000003.132] D/socket connect to 124.71.128.165,9108
[2026-07-21 16:51:05.987][000000003.133] I/user.[excloud]TCP连接结果 true false
[2026-07-21 16:51:05.997][000000003.133] I/user.[excloud]excloud service started
[2026-07-21 16:51:06.008][000000003.134] I/user.[excloud]excloud 自动心跳已启动,间隔 300 秒
[2026-07-21 16:51:06.018][000000003.170] I/user.[excloud]TCP socket cb userdata: 0C1859C8 33554449 0
[2026-07-21 16:51:06.024][000000003.170] I/user.[excloud]TCP socket TCP连接成功
[2026-07-21 16:51:06.028][000000003.171] I/user.app_vl53l1x excloud连接成功
[2026-07-21 16:51:06.038][000000003.171] I/mobile l_mobile_muid called
[2026-07-21 16:51:06.048][000000003.173] I/mobile l_mobile_muid ret=32
[2026-07-21 16:51:06.053][000000003.173] I/user.[excloud] 发送鉴权请求
[2026-07-21 16:51:06.056][000000003.173] I/user.[excloud]构建发送数据 field: 16 type: 3 value: PhbD7kCQg9Jt7vwCGgtdjQ6jIscQ2gJJ-864317083866528-20260702152525A860510A9885483051
[2026-07-21 16:51:06.061][000000003.175] I/user.[excloud]tlv发送数据长度4 85
[2026-07-21 16:51:06.069][000000003.175] I/user.[excloud]构建消息头 seq: 2 len: 85 flags: 18 dev: 0186431708386652
[2026-07-21 16:51:06.082][000000003.177] I/user.app_vl53l1x excloud发送成功, 流水号: 2
[2026-07-21 16:51:06.087][000000003.178] I/user.[excloud]数据发送成功 101 字节
[2026-07-21 16:51:06.090][000000003.230] I/user.[excloud]TCP socket cb userdata: 0C1859C8 33554450 0
[2026-07-21 16:51:06.094][000000003.230] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:51:06.104][000000003.275] I/user.[excloud]TCP socket cb userdata: 0C1859C8 33554452 0
[2026-07-21 16:51:06.109][000000003.276] I/user.[excloud]TCP socket 收到数据 22 字节 01864317083866520001000600000000301100026F6B 44
[2026-07-21 16:51:06.115][000000003.277] I/user.[excloud]解析消息头 设备ID: 864317083866528 序列号: 1 消息长度: 6 协议版本: 0 需要回复: false UDP承载: false 包含auth_key: false
[2026-07-21 16:51:06.120][000000003.278] I/user.[excloud]鉴权成功 ok
[2026-07-21 16:51:06.124][000000003.279] I/user.app_vl53l1x excloud认证成功
[2026-07-21 16:51:06.127][000000003.281] I/user.[excloud]构建发送数据 field: 20 type: 5 value: 258
[2026-07-21 16:51:06.137][000000003.282] I/user.[excloud]构建发送数据 field: 20 type: 5 value: FOTA升级成功: 升级前: PROJECT=Air8780P_VL53L1X VERSION=001.999.000 ; 升级后: PROJECT=Air8780P_VL53L1X VERSION=001.999.001
[2026-07-21 16:51:06.140][000000003.283] I/user.[excloud]tlv发送数据长度4 142
[2026-07-21 16:51:06.149][000000003.284] I/user.[excloud]构建消息头 seq: 3 len: 142 flags: 2 dev: 0186431708386652
[2026-07-21 16:51:06.154][000000003.286] I/user.app_vl53l1x excloud发送成功, 流水号: 3
[2026-07-21 16:51:06.158][000000003.287] I/user.[excloud]数据发送成功 158 字节
[2026-07-21 16:51:06.166][000000003.287] I/user.app_vl53l1x excloud数据上报成功
[2026-07-21 16:51:06.170][000000003.289] I/user.app_vl53l1x MQTT上报数据到: 864317083866528/vl53l1x/up
[2026-07-21 16:51:06.174][000000003.293] dns_run 676:lbsmqtt.airm2m.com state 0 id 2 ipv6 0 use dns server0, try 0
[2026-07-21 16:51:06.181][000000003.339] I/user.[excloud]TCP socket cb userdata: 0C1859C8 33554450 0
[2026-07-21 16:51:06.186][000000003.340] I/user.[excloud]TCP socket 发送完成
[2026-07-21 16:51:06.191][000000003.356] dns_run 693:dns all done ,now stop
[2026-07-21 16:51:06.196][000000003.476] I/user.app_vl53l1x MQTT事件: conack nil
[2026-07-21 16:51:06.200][000000003.476] I/user.app_vl53l1x MQTT连接成功
[2026-07-21 16:51:06.205][000000003.478] I/user.app_vl53l1x MQTT已发布: {"fota_info":"FOTA升级成功: 升级前: PROJECT=Air8780P_VL53L1X VERSION=001.999.000 ; 升级后: PROJECT=Air8780P_VL53L1X VERSION=001.999.001","distance_mm":258,"project":"Air8780P_VL53L1X","distances":[260,258,257],"imei":"864317083866528","valid_samples":3,"ts":1784623867,"version":"001.999.001"}
[2026-07-21 16:51:06.215][000000003.550] I/user.app_vl53l1x MQTT事件: sent 1
[2026-07-21 16:51:06.219][000000003.551] I/user.app_vl53l1x MQTT数据发送成功
[2026-07-21 16:51:06.229][000000003.552] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:3
[2026-07-21 16:51:06.233][000000003.554] I/user.app_vl53l1x MQTT事件: disconnect 0
[2026-07-21 16:51:06.240][000000003.554] W/user.app_vl53l1x MQTT断开
[2026-07-21 16:51:06.244][000000003.555] I/user.app_vl53l1x MQTT事件: close nil
[2026-07-21 16:51:06.610][000000004.054] W/mqtt network_tx ret -1, closing socket
[2026-07-21 16:51:06.616][000000004.054] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:0
[2026-07-21 16:51:06.621][000000004.054] I/mqtt mqtt closing socket netc:c1b7f28 mqtt_state:0
[2026-07-21 16:51:06.625][000000004.055] I/user.app_vl53l1x MQTT短连接结束,发送 成功
[2026-07-21 16:51:06.635][000000004.055] I/user.app_vl53l1x 发送完成,进入PSM+模式
[2026-07-21 16:51:06.639][000000004.055] I/user.app_vl53l1x 准备进入PSM+模式,休眠5分钟后唤醒
[2026-07-21 16:51:06.651][000000004.056] I/user.app_vl53l1x 关闭VL53L1X传感器
[2026-07-21 16:51:06.655][000000004.056] I/user.app_vl53l1x GPIO27 LOW
[2026-07-21 16:51:06.663][000000004.057] I/user.app_vl53l1x 深度休眠定时器已设置: 300000 ms
[2026-07-21 16:51:06.922][000000004.367] net_lwip_tcp_err_cb 662:adapter 1 socket 1 not closing, but error -13
```
### 5.6 故障排除
1. **VL53L1X 传感器初始化失败**
- 检查 I2C 接线是否正确(GPIO1-SCL、GPIO2-SDA、3.3V、GND
- 确认传感器供电正常(3.3V
- 检查传感器 I2C 地址是否默认 0x29(可通过读写 0x010F 寄存器验证,应返回 0xEA)
2. **测距数据一直为无效(串扰信号)**
- 传感器前方可能有遮挡物
- 传感器距离目标过近(< 10mm)
- 环境光太强或目标表面反射率过低
- 可通过 `calibrate_xtalk()` API 进行串扰校准
3. **无法联网**
- 检查 SIM 卡是否正确安装、是否有流量
- 检查天线是否连接
- 检查网络信号强度
4. **无法进入 PSM+ 模式**
- 检查是否有其他任务未阻塞
- 确认 `pm.power(pm.WORK_MODE, 3)` 已执行
5. **PSM+ 模式下传感器功耗偏高(320µA)**
- 传感器模块始终上电,软件待机下模块上的 LDO/电平转换芯片仍会消耗电流
- 如需降低,可使用 VL53L1X 的 XSHUT 引脚(硬件断电),接线时把 XSHUT 接到模组空闲 GPIO
`setup` 时传入 `{xshut = GPIO号}`,进入 PSM+ 前会自动拉低 XSHUT 彻底关断传感器
5. **MQTT 数据未收到**
- 确认 Topic 正确(格式:`IMEI/vl53l1x/up`
- 检查 MQTT 服务器地址和端口配置是否正确
- 日志中查看 `MQTT数据发送成功` 是否打印
### 5.7 扩展功能建议
- 串扰校准:使用 `exs_vl53l1x.calibrate_xtalk()` API 配合白纸板进行校准,校准值传入 `setup({xtalk_offset = value})` 即可
- 中断模式:VL53L1X 支持 GPIO1 中断,可配置为回调模式或轮询模式,详见 [exs_vl53l1x 扩展库说明](https://docs.openluat.com/osapi/ext/sensor/exs_vl53l1x/)
- 三档测距模式:支持 standard(约 2.9m/ short(约 1.36m,抗强光)/ long(约 4.6m)三种模式,在 `setup` 时通过 `range_mode` 参数设置
@@ -0,0 +1,239 @@
--[[
@module sensor_vl53l1x
@summary Air8780P VL53L1X激光测距传感器驱动封装模块
@version 1.0
@date 2026.07.23
@author 江访
@usage
本文件为VL53L1X激光测距传感器的驱动封装模块,核心业务逻辑为:
1、传感器初始化(I2C配置、模式选择)
2、传感器数据采集(多次采样取平均)
3、传感器关闭
本文件的对外接口有3个:
1、sensor_vl53l1x.init(scl, sda, range_mode):初始化传感器
2、sensor_vl53l1x.collect_data(sensor_samples):采集传感器数据,返回平均值
3、sensor_vl53l1x.close():关闭传感器
]]
-- ==================== 加载扩展库 ====================
-- exs_vl53l1x: VL53L1X传感器扩展库,提供底层I2C通信和测距控制
-- 使用前需确认固件中包含此扩展库
local exs_vl53l1x = require "exs_vl53l1x"
-- ==================== 模块表 ====================
-- sensor_vl53l1x 为模块对外接口表,所有对外函数注册在此表上
-- 调用方通过 local sensor_vl53l1x = require "sensor_vl53l1x" 获取本表
local sensor_vl53l1x = {}
-- ==================== API:初始化 ====================
--[[
初始化VL53L1X激光测距传感器
sensor_vl53l1x.init(scl, sda, range_mode)
本函数完成以下工作:
1、配置I2C引脚(软件I2C模式)
2、等待传感器固件就绪
3、写入预设模式配置(standard/short/long
4、校验芯片IDMID=0xEA, MT=0xCC
@param number scl
含义:I2C时钟线SCL的GPIO引脚号
取值范围:0~最大GPIO号,视具体模组而定
是否必选:必选
示例值:1Air8780P上的GPIO1
@param number sda
含义:I2C数据线SDA的GPIO引脚号
取值范围:0~最大GPIO号,视具体模组而定
是否必选:必选
示例值:2Air8780P上的GPIO2
@param string range_mode
含义:测距模式
取值范围:
"short" — 短距离模式,最远约1.36m,抗环境光能力强
"standard"— 标准模式,最远约2.9m,通用场景
"long" — 长距离模式,最远约4.6m,需要较好的环境光条件
是否必选:可选
默认值:"standard"
@return boolean
含义:初始化是否成功
取值范围:
true — 初始化成功,传感器可正常使用
false — 初始化失败,请检查I2C接线和传感器供电
@usage
-- 基本用法:GPIO1=SCL, GPIO2=SDA, 标准模式
local ok = sensor_vl53l1x.init(1, 2, "standard")
if not ok then
log.error("sensor_vl53l1x", "传感器初始化失败")
return
end
-- 短距离模式(抗强光)
local ok = sensor_vl53l1x.init(1, 2, "short")
-- 长距离模式
local ok = sensor_vl53l1x.init(1, 2, "long")
]]
function sensor_vl53l1x.init(scl, sda, range_mode)
range_mode = range_mode or "standard"
log.info("sensor_vl53l1x", "正在初始化VL53L1X传感器, mode=" .. range_mode)
-- 调用扩展库setup接口,传入I2C引脚和测距模式
-- exs_vl53l1x.setup内部会完成:
-- 1. i2c.setup初始化I2C总线
-- 2. 等待传感器固件启动(轮询寄存器0x00E5)
-- 3. 写入预设模式配置
-- 4. 验证芯片型号
local ok = exs_vl53l1x.setup({
scl = scl,
sda = sda,
range_mode = range_mode,
})
if not ok then
log.error("sensor_vl53l1x", "VL53L1X初始化失败,请检查接线和供电")
return false
end
log.info("sensor_vl53l1x", "VL53L1X初始化成功")
return true
end
-- ==================== API:数据采集 ====================
--[[
采集传感器数据,多次采样后取平均值
sensor_vl53l1x.collect_data(sensor_samples)
本函数完成以下工作:
1、按指定次数循环采集测距数据
2、每次采集间隔500ms,等待传感器就绪
3、过滤无效数据(status不为0的帧)
4、计算有效数据的平均值(四舍五入取整)
注意:本函数内部有 sys.wait(),必须在协程中调用
@param number sensor_samples
含义:每次唤醒后采集的次数
取值范围:正整数,建议1~10
1 — 单次采集,速度快但可能因瞬间干扰产生偏差
3 — 默认值,三次取平均,兼顾速度和精度
5~10 — 多次采集,精度高但耗时较长(每次间隔500ms)
是否必选:可选
默认值:3
@return table
含义:采集成功时返回数据表,包含以下字段
distance_list : table,原始距离值数组(单位mm),例如 {267, 261, 265}
avg_distance : number,有效数据的平均值(单位mm,四舍五入取整)
valid_count : number,有效数据的帧数
@return nil
含义:所有采集数据均无效时返回nil
可能原因:
1、传感器前方有遮挡物
2、传感器距离目标过近(<10mm)
3、环境光过强或目标表面反射率过低
@usage
-- 默认采集3次
local data = sensor_vl53l1x.collect_data(3)
if data then
log.info("sensor_vl53l1x", "平均距离=" .. data.avg_distance .. "mm")
log.info("sensor_vl53l1x", "原始数据=" .. table.concat(data.distance_list, ","))
else
log.warn("sensor_vl53l1x", "未测到有效距离")
end
]]
function sensor_vl53l1x.collect_data(sensor_samples)
sensor_samples = sensor_samples or 3
log.info("sensor_vl53l1x", "开始采集传感器数据,采集次数=" .. sensor_samples)
-- distances: 存储有效距离值的数组
-- valid_count: 有效数据帧数计数器
local distances = {}
local valid_count = 0
-- 循环采集:每次采集前等待500ms让传感器完成测距
for i = 1, sensor_samples do
sys.wait(500)
-- exs_vl53l1x.get_data() 返回数据结构:
-- {
-- distance = number, -- 测距值(单位mm
-- status = number, -- 状态码,0=成功
-- status_str = string, -- 状态描述文字
-- sigma = number, -- 标准差(可选)
-- }
local data = exs_vl53l1x.get_data()
-- 判断数据是否有效:status==0 表示测距成功
if data and data.status == 0 then
distances[#distances + 1] = data.distance
valid_count = valid_count + 1
log.info("sensor_vl53l1x", string.format("采集[%d/%d] 距离=%dmm 状态=%s",
i, sensor_samples, data.distance, data.status_str))
else
local status_str = (data and data.status_str) or "无数据"
log.warn("sensor_vl53l1x", string.format("采集[%d/%d] 无效数据 状态=%s",
i, sensor_samples, status_str))
end
end
-- 没有有效数据时返回nil
if valid_count == 0 then
log.error("sensor_vl53l1x", "所有采集数据均无效")
return nil
end
-- 计算平均值(四舍五入取整)
local sum = 0
for _, d in ipairs(distances) do
sum = sum + d
end
local avg_distance = math.floor(sum / #distances + 0.5)
log.info("sensor_vl53l1x", string.format("采集完成: 有效%d帧, 平均距离=%dmm",
valid_count, avg_distance))
-- 返回数据表
return {
distance_list = distances, -- 原始距离数组,用于MQTT上报详细数据
avg_distance = avg_distance, -- 平均距离,用于excloud上报
valid_count = valid_count, -- 有效帧数,用于判断数据质量
}
end
-- ==================== API:关闭传感器 ====================
--[[
关闭VL53L1X传感器
sensor_vl53l1x.close()
本函数完成以下工作:
1、调用扩展库close接口停止测距
2、释放I2C总线资源
3、传感器进入待机模式,降低功耗
建议在以下场景调用:
1、数据采集完成后立即关闭,减少功耗
2、进入PSM+休眠前必须关闭
3、设备异常重启前
@usage
sensor_vl53l1x.close()
]]
function sensor_vl53l1x.close()
log.info("sensor_vl53l1x", "关闭VL53L1X传感器")
exs_vl53l1x.close()
end
return sensor_vl53l1x