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,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