init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码 - 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等) - 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
## 功能模块介绍
|
||||
|
||||
### iot服务器fota功能演示
|
||||
|
||||
1、main.lua:主程序入口;
|
||||
|
||||
2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
|
||||
|
||||
3、update.lua:使用合宙iot服务器进行远程升级功能模块,简单升级演示;
|
||||
|
||||
4、tcp_iot文件夹:通过tcp服务器下发升级指令(指令格式使用json字符串,包含是否升级参数),控制设备启动air_srv_fota功能模块,使用合宙iot服务器进行升级;
|
||||
|
||||
5、air_srv_fota.lua:合宙服务器升级功能模块;
|
||||
|
||||
6、psm_power_fota.lua:低功耗fota功能模块,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子。需要注意的是此场景与上面两种场景不能同时使用;
|
||||
|
||||
|
||||
## 系统消息介绍
|
||||
|
||||
1、"IP_READY":某种网卡已经获取到ip信息,仅仅获取到了ip信息,能否和外网连通还不确认;
|
||||
|
||||
2、"IP_LOSE":某种网卡已经掉网;
|
||||
|
||||
|
||||
|
||||
## 用户消息介绍
|
||||
|
||||
1、"RECV_DATA_FROM_SERVER":socket client收到服务器下发的数据后,通过此消息发布出去,给其他应用模块处理;
|
||||
|
||||
2、"SEND_DATA_REQ":其他应用模块发布此消息,通知socket client发送数据给服务器;
|
||||
|
||||
|
||||
|
||||
## 演示功能概述
|
||||
|
||||
1、此demo演示了三种场景:
|
||||
|
||||
(1) fota升级简单演示:使用合宙iot服务器进行远程升级功能模块,简单升级演示;
|
||||
|
||||
(2) tcp服务器下发升级指令:通过tcp服务器下发升级指令(指令格式使用json字符串,包含是否升级参数),控制设备使用fota功能模块;
|
||||
|
||||
(3) psm低功耗fota:低功耗fota功能模块,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子;
|
||||
|
||||
2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
|
||||
|
||||
(1) netdrv_4g:4G网卡
|
||||
|
||||
(2) netdrv_wifi:WIFI STA网卡
|
||||
|
||||
(3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
|
||||
|
||||
(4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
|
||||
|
||||
|
||||
## 演示硬件环境
|
||||
|
||||

|
||||
|
||||
1、Air780EPM V1.3版本开发板一块+可上网的sim卡一张+4g天线一根+网线一根:
|
||||
|
||||
- sim卡插入开发板的sim卡槽
|
||||
|
||||
- 天线装到开发板上
|
||||
|
||||
- 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
|
||||
|
||||
2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EPM V1.3版本开发板和数据线的硬件接线方式为:
|
||||
|
||||
- Air780EPM V1.3版本开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
|
||||
|
||||
- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
|
||||
|
||||
|
||||
|
||||
## 演示软件环境
|
||||
|
||||
1、Luatools下载调试工具
|
||||
|
||||
2、[Air780EPM V2012版本固件)](https://docs.openluat.com/air780epm/luatos/firmware/version/)
|
||||
|
||||
3、PC端浏览器访问[合宙TCP/UDP web测试工具](https://iot.luatos.com/#/page6/netlab);
|
||||
|
||||
|
||||
## 演示核心步骤
|
||||
|
||||
1、搭建好硬件环境
|
||||
|
||||
2、PC端浏览器访问[合宙TCP/UDP web测试工具](https://iot.luatos.com/#/page6/netlab),点击 打开TCP 按钮,会创建一个TCP server,将server的地址和端口赋值给tcp_iot_main.lua中的SERVER_ADDR和SERVER_PORT两个变量
|
||||
|
||||
详细使用说明参考:[合宙 TCP/UDP web 测试工具使用说明](https://iot.luatos.com/#/p8000/netlab) 。
|
||||
|
||||
4、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
|
||||
|
||||
- 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
|
||||
|
||||
- 如果需要单WIFI STA网卡,打开require "netdrv_wifi",其余注释掉;同时netdrv_wifi.lua中的wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
|
||||
|
||||
- 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
|
||||
|
||||
- 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
|
||||
|
||||
5、Luatools烧录内核固件和修改后的demo脚本代码
|
||||
|
||||
6、使用Luatools制作升级包,先把新旧版本分别生成量产文件,然后再制作升级包,工具上栏 luatOS->固件工具->差分包/整包升级包制作,将制作好的升级包配置到合宙iot服务器自己项目下,或上传到自建服务器上面;
|
||||
|
||||
7、烧录成功后,自动开机运行
|
||||
|
||||
8、可以看到升级过程如下,不管是什么场景下升级,基本都是如下日志情况:
|
||||
|
||||
``` lua
|
||||
--没有升级之前可以看到如下打印
|
||||
I/user.fota 脚本版本号 001.000.000 core版本号 V2010
|
||||
|
||||
I/user.fota_task_func recv IP_READY 1
|
||||
I/user.开始检查升级
|
||||
|
||||
I/user.升级包下载成功,重启模块
|
||||
|
||||
|
||||
--升级之后可以看到如下打印
|
||||
I/user.fota 脚本版本号 001.000.001 core版本号 V2012
|
||||
--升级重启之后还是会检查升级,所以会有如下打印属于正常情况,其中"code": 27 是合宙iot服务器返回的状态码,意思是已经是最新版本了。
|
||||
I/user.fota -9 {"code": 27, "msg": "\u5df2\u662f\u6700\u65b0\u7248\u672c"}
|
||||
I/user.使用合宙服务器,接下来解析body里的code
|
||||
I/user.已是最新版本 1.设备的固件/脚本版本高于或等于云平台上的版本号 2.用户项目升级配置中未添加该设备 3.云平台升级配置中,是否升级配置为否
|
||||
I/user.fota 4
|
||||
|
||||
|
||||
```
|
||||
9、对于psm休眠状态下的升级的场景,可以通过iot平台查看是否成功,在iot平台的升级日志页面搜索模组的imei,可以看到有两条升级结果“成功”和“已是最新版本”。模组升级成功后会自动进入psm休眠状态。可以通过电流查看休眠情况。
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
--[[
|
||||
@module air_srv_fota
|
||||
@summary 使用合宙iot平台远程升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
实现远程升级功能,具体流程如下:
|
||||
1、接收 AIR_SRV_FOTA 系统消息,触发升级;
|
||||
2、判断网卡是否连接成功;
|
||||
3、初始化fota2模块;
|
||||
4、调用fota2模块的升级函数;
|
||||
5、根据升级结果进行处理;
|
||||
]]
|
||||
|
||||
-- 使用合宙iot平台时需要这个参数
|
||||
PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
|
||||
|
||||
--加在libfota2扩展库
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
|
||||
-- 循环打印版本号, 方便看版本号变化, 非必须
|
||||
function get_version()
|
||||
log.info("降功耗 找合宙")
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
end
|
||||
|
||||
sys.timerLoopStart(get_version, 3000)
|
||||
|
||||
|
||||
-- fota升级标志:true 表示当前正有 FOTA 流程在跑
|
||||
local fota_running = false
|
||||
|
||||
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
-- fota结束,无论成功还是失败,都释放fota_running标志
|
||||
fota_running = false
|
||||
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
|
||||
"3) 已经是最新版本,无需升级")
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
-- 使用合宙iot平台进行升级, 支持自定义参数, 也可以不配置,如果要配置参数可以参考此链接https://docs.openluat.com/osapi/ext/libfota2/
|
||||
local opts = {}
|
||||
|
||||
local function air_fota_func(data)
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
while true do
|
||||
-- 阻塞等待外部事件:"AIR_SRV_FOTA"
|
||||
sys.waitUntil("AIR_SRV_FOTA")
|
||||
|
||||
-- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
|
||||
if "123" == _G.PRODUCT_KEY then
|
||||
while true do
|
||||
sys.wait(1000)
|
||||
log.info("fota", "请修改正确的PRODUCT_KEY")
|
||||
end
|
||||
end
|
||||
|
||||
if fota_running then
|
||||
log.warn("fota_task", "FOTA 正在运行,跳过本次请求")
|
||||
else
|
||||
-- 标记FOTA正在运行
|
||||
-- 注意:这里只是标记,实际的FOTA流程还没有开始
|
||||
fota_running = true
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 初始化FOTA任务
|
||||
sys.taskInit(air_fota_func)
|
||||
|
||||
|
||||
-- 演示定时自动升级, 每隔4小时自动检查一次,可以根据需求打开
|
||||
-- sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, opts)
|
||||
@@ -0,0 +1,83 @@
|
||||
--[[
|
||||
@module main
|
||||
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本demo演示的核心功能为:
|
||||
1、此demo演示了三种场景:
|
||||
(1)fota升级简单演示:使用合宙iot服务器进行远程升级功能模块,简单升级演示;
|
||||
(2)tcp服务器下发升级指令:通过tcp服务器下发升级指令(指令格式使用json字符串,包含是否升级参数),控制设备使用fota功能模块;
|
||||
(3)psm低功耗fota:低功耗fota功能模块,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子;
|
||||
2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
|
||||
(1) netdrv_4g:4G网卡
|
||||
(2) netdrv_wifi:WIFI STA网卡
|
||||
(3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
|
||||
(4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
|
||||
|
||||
更多说明参考本目录下的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这三位数字必须存在,但是没有任何用处,可以一直写为999
|
||||
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
|
||||
]]
|
||||
PROJECT = "FOTA2_DEMO"
|
||||
VERSION = "001.999.000"
|
||||
|
||||
|
||||
-- 在日志中打印项目名和项目版本号
|
||||
log.info("main", PROJECT, VERSION)
|
||||
|
||||
|
||||
|
||||
|
||||
-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
|
||||
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
|
||||
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
|
||||
-- 启动errDump日志存储并且上传功能,600秒上传一次
|
||||
-- if errDump then
|
||||
-- errDump.config(true, 600)
|
||||
-- end
|
||||
|
||||
|
||||
|
||||
-- 启动一个循环定时器
|
||||
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
|
||||
-- 方便分析内存使用是否有异常
|
||||
-- sys.timerLoopStart(function()
|
||||
-- log.info("mem.lua", rtos.meminfo())
|
||||
-- log.info("mem.sys", rtos.meminfo("sys"))
|
||||
-- end, 3000)
|
||||
|
||||
|
||||
-- 加载网络驱动设备功能模块
|
||||
require "netdrv_device"
|
||||
|
||||
-- 加载远程升级功能模块,场景1
|
||||
require "update"
|
||||
---------------------------------------------------------------------------
|
||||
-- 加载tcp client iot socket主应用功能模块,通过tcp服务器下发升级指令(指令格式使用json字符串,包含是否升级参数),控制设备使用fota功能模块,场景二
|
||||
-- require "tcp_iot_main"
|
||||
-- 加载合宙iot平台远程升级功能模块
|
||||
-- require "air_srv_fota"
|
||||
---------------------------------------------------------------------------
|
||||
-- 加载psm+低功耗模式升级功能模块,场景三
|
||||
-- require "psm_power_fota"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- 用户代码已结束---------------------------------------------
|
||||
-- 结尾总是这一句
|
||||
sys.run()
|
||||
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
@module netdrv_4g
|
||||
@summary “4G网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.01
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为4G网卡驱动模块,核心业务逻辑为:
|
||||
1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
|
||||
]]
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.LWIP_GP then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_4g.ip_ready_func", "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_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
|
||||
-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
-- 在Air780EPM上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
--[[
|
||||
@module netdrv_eth_spi
|
||||
@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.24
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
|
||||
1、打开CH390H芯片供电开关;
|
||||
2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
|
||||
3、以太网卡的连接状态发生变化时,在日志中进行打印;
|
||||
|
||||
直接使用Air780EPM V1.3版本开发板硬件测试即可;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
|
||||
]]
|
||||
|
||||
local exnetif = require "exnetif"
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.LWIP_ETH then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
|
||||
end
|
||||
end
|
||||
|
||||
local function ip_lose_func(adapter)
|
||||
if adapter == socket.LWIP_ETH then
|
||||
log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
|
||||
-- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
|
||||
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
|
||||
|
||||
-- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
|
||||
-- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
|
||||
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
|
||||
|
||||
--此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
|
||||
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
|
||||
local function netdrv_eth_spi_task_func()
|
||||
-- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
|
||||
-- 本demo使用Air780EPM V1.3版本开发板测试,开发板上的硬件配置为:
|
||||
-- GPIO20为CH390H以太网芯片的供电使能控制引脚
|
||||
-- 使用spi0,片选引脚使用GPIO8,中断引脚使用GPIO22
|
||||
-- 如果使用的硬件和以上描述的环境不同,根据自己的硬件配置修改以下参数
|
||||
exnetif.set_priority_order({
|
||||
{
|
||||
ETHERNET = {
|
||||
pwrpin = 20,
|
||||
tp = netdrv.CH390,
|
||||
opts = {spi = 0, cs = 8, irq = 22}
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
-- 启动一个task,task的处理函数为netdrv_eth_spi_task_func
|
||||
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
|
||||
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
|
||||
sys.taskInit(netdrv_eth_spi_task_func)
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
--[[
|
||||
@module netdrv_multiple
|
||||
@summary 多网卡(4G网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.24
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为多网卡驱动模块,核心业务逻辑为:
|
||||
1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
|
||||
|
||||
直接使用Air780EPM V1.3版本开发板硬件测试即可;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
|
||||
]]
|
||||
|
||||
local exnetif = require "exnetif"
|
||||
|
||||
-- 网卡状态变化通知回调函数
|
||||
-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
|
||||
-- 当网卡切换切换时:
|
||||
-- net_type:string类型,表示当前使用的网卡字符串
|
||||
-- adapter:number类型,表示当前使用的网卡id
|
||||
-- 当所有网卡断网时:
|
||||
-- net_type:为nil
|
||||
-- adapter:number类型,为-1
|
||||
local function netdrv_multiple_notify_cbfunc(net_type,adapter)
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
if type(net_type)=="string" then
|
||||
log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
|
||||
elseif type(net_type)=="nil" then
|
||||
log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
|
||||
else
|
||||
log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
|
||||
end
|
||||
end
|
||||
|
||||
local function netdrv_multiple_task_func()
|
||||
--设置网卡优先级
|
||||
exnetif.set_priority_order(
|
||||
{
|
||||
-- “通过SPI外挂CH390H芯片”的以太网卡,使用Air780EPM V1.3版本开发板验证
|
||||
{
|
||||
ETHERNET = {
|
||||
-- 供电使能GPIO
|
||||
pwrpin = 20,
|
||||
-- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
|
||||
-- 如果没有传入此参数,exnetif会使用默认值10秒
|
||||
ping_time = 3000,
|
||||
|
||||
-- 连通性检测ip(选填参数);
|
||||
-- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
|
||||
-- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
|
||||
-- ping_ip = "填入可靠的并且可以ping通的ip地址",
|
||||
|
||||
-- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
|
||||
tp = netdrv.CH390,
|
||||
opts = {spi=0, cs=8, irq=22}
|
||||
}
|
||||
},
|
||||
|
||||
-- 4G网卡
|
||||
{
|
||||
LWIP_GP = true
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
|
||||
exnetif.notify_status(netdrv_multiple_notify_cbfunc)
|
||||
|
||||
-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
|
||||
-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
|
||||
-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
|
||||
|
||||
|
||||
-- 启动一个task,task的处理函数为netdrv_multiple_task_func
|
||||
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
|
||||
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
|
||||
sys.taskInit(netdrv_multiple_task_func)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
--[[
|
||||
@module netdrv_pc
|
||||
@summary “pc模拟器网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.01
|
||||
@author 朱天华
|
||||
@usage
|
||||
本文件为pc模拟器网卡驱动模块,核心业务逻辑为:
|
||||
1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_pc"就可以加载运行;
|
||||
]]
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.ETH0 then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_pc.ip_ready_func", "IP_READY", socket.localIP(socket.ETH0))
|
||||
end
|
||||
end
|
||||
|
||||
local function ip_lose_func(adapter)
|
||||
if adapter == socket.ETH0 then
|
||||
log.warn("netdrv_pc.ip_lose_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察pc模拟器网络的连接状态
|
||||
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
-- 设置默认网卡为socket.ETH0
|
||||
-- pc模拟器上的默认网卡仍然需要使用接口(socket.ETH0)来设置,因为exnetif扩展库当前还不支持模拟器
|
||||
socket.dft(socket.ETH0)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
--[[
|
||||
@module netdrv_device
|
||||
@summary 网络驱动设备功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
|
||||
1、netdrv_4g:socket.LWIP_GP,4G网卡;
|
||||
2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
|
||||
3、netdrv_eth_spi:socket.LWIP_USER1,通过SPI外挂CH390H芯片的以太网卡;
|
||||
4、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
|
||||
|
||||
根据自己的项目需求,只需要require以上四种中的一种即可;
|
||||
|
||||
|
||||
本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
|
||||
]]
|
||||
|
||||
|
||||
-- 根据自己的项目需求,只需要require以下四种中的一种即可;
|
||||
|
||||
-- 加载“4G网卡”驱动模块
|
||||
require "netdrv_4g"
|
||||
|
||||
-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
|
||||
-- require "netdrv_eth_spi"
|
||||
|
||||
-- 加载“可以配置优先级的多种网卡”驱动模块
|
||||
-- require "netdrv_multiple"
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
--[[
|
||||
@module psm+_power_fota
|
||||
@summary psm+超低功耗模式下升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为psm+超低功耗模式下升级功能模块,核心设计思路
|
||||
1.升级触发机制 :
|
||||
- 定时器唤醒升级 :设备定期从PSM模式唤醒,主动检查是否有新固件版本。
|
||||
- 外部中断唤醒升级 :通过特定GPIO中断或网络消息唤醒设备进行升级。
|
||||
2.防止升级过程中进入休眠 :
|
||||
- 在开始FOTA升级前,禁用PSM模式进入。
|
||||
- 升级完成后,根据结果决定是否重启设备或重新进入PSM模式。
|
||||
|
||||
]]
|
||||
-- 使用合宙iot平台时需要这个参数
|
||||
PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
|
||||
--加在libfota2扩展库
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
--升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
|
||||
sys.publish("FOTA_END")
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
|
||||
"3) 已经是最新版本,无需升级")
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- 使用合宙iot平台进行升级, 支持自定义参数, 也可以不配置,如果要配置参数可以参考此链接https://docs.openluat.com/osapi/ext/libfota2/
|
||||
local ota_opts = {}
|
||||
|
||||
|
||||
function psm_fota_task_func()
|
||||
-- 如果是被定时器唤醒,因为上次进入PSM+时是开启了飞行模式,所以在唤醒后第一时间关闭飞行模式。
|
||||
mobile.flymode(0, false)
|
||||
log.info("开始测试PSM+模式功耗。")
|
||||
|
||||
-- 打印版本号, 方便看版本号变化, 非必须
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, ota_opts)
|
||||
|
||||
|
||||
|
||||
-- 等待下载升级包结束, 发布消息"FOTA_END",
|
||||
-- 如果15秒内没有收到消息,则15秒的时长到达后进入PSM+模式。
|
||||
-- 需要注意的是在fota_cb回调函数中,升级包下载成功后,会立马重启并升级模组。如果还有其他事情要做不想立马重启升级,需自行决定reboot的时机
|
||||
-- 升级包下载成功后,本demo默认是立即自动重启并且将升级包更新到模组中,更新成功后,会再次走到这里
|
||||
-- 再次走到这里后,合宙iot平台会返回“已经是最新版本,不需要升级”,fota_cb回调函数中会发布消息"FOTA_END"
|
||||
-- 至此,才会继续向下执行代码,进入PSM+模式
|
||||
sys.waitUntil("FOTA_END", 15000)
|
||||
|
||||
log.info("升级结束,进入PSM模式")
|
||||
|
||||
|
||||
|
||||
-- 定时检查升级 (每4小时唤醒一次)
|
||||
pm.dtimerStart(2, 4 * 3600000)
|
||||
-- 启动飞行模式,规避可能会出现的网络问题
|
||||
mobile.flymode(0, true)
|
||||
-- 进入PSM模式
|
||||
pm.power(pm.WORK_MODE, 3)
|
||||
-- 防御机制:15秒后如果未进入PSM则重启
|
||||
sys.wait(15000)
|
||||
log.info("进入PSM+失败,重启")
|
||||
rtos.reboot()
|
||||
end
|
||||
|
||||
sys.taskInit(psm_fota_task_func)
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
--[[
|
||||
@module tcp_client_main
|
||||
@summary tcp client socket主应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket主应用功能模块,核心业务逻辑为:
|
||||
1、创建一个tcp client socket,连接server;
|
||||
2、处理连接异常,出现异常后执行重连动作;
|
||||
3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
|
||||
|
||||
本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
|
||||
]]
|
||||
|
||||
local libnet = require "libnet"
|
||||
|
||||
-- 加载tcp client socket数据接收功能模块
|
||||
local tcp_client_receiver = require "tcp_iot_receiver"
|
||||
-- 加载tcp client socket数据发送功能模块
|
||||
local tcp_client_sender = require "tcp_iot_sender"
|
||||
|
||||
-- 电脑访问:https://iot.luatos.com/#/page6/netlab
|
||||
-- 本工具使用方法可以参考:https://docs.openluat.com/common/TCPUDP_Test/
|
||||
-- 点击 打开TCP 按钮,会创建一个TCP server
|
||||
-- 将server的地址和端口赋值给下面这两个变量
|
||||
local SERVER_ADDR = "112.125.89.8"
|
||||
local SERVER_PORT = 42662
|
||||
|
||||
-- tcp_client_main的任务名
|
||||
local TASK_NAME = tcp_client_sender.TASK_NAME
|
||||
|
||||
|
||||
-- 处理未识别的消息
|
||||
local function tcp_client_main_cbfunc(msg)
|
||||
log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
|
||||
end
|
||||
|
||||
-- tcp client socket的任务处理函数
|
||||
local function tcp_client_main_task_func()
|
||||
local socket_client
|
||||
local result, para1, para2
|
||||
|
||||
while true do
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("sntp_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("tcp_client_main_task_func", "recv IP_READY", socket.dft())
|
||||
|
||||
-- 创建socket client对象
|
||||
socket_client = socket.create(nil, TASK_NAME)
|
||||
-- 如果创建socket client对象失败
|
||||
if not socket_client then
|
||||
log.error("tcp_client_main_task_func", "socket.create error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
-- 配置socket client对象为tcp client
|
||||
result = socket.config(socket_client, nil, nil, nil, 300, 10, 3)
|
||||
-- 如果配置失败
|
||||
if not result then
|
||||
log.error("tcp_client_main_task_func", "socket.config error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
-- 连接server
|
||||
result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
|
||||
-- 如果连接server失败
|
||||
if not result then
|
||||
log.error("tcp_client_main_task_func", "libnet.connect error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
log.info("tcp_client_main_task_func", "libnet.connect success")
|
||||
|
||||
-- 数据收发以及网络连接异常事件总处理逻辑
|
||||
while true do
|
||||
-- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
|
||||
-- 如果处理失败,则退出循环
|
||||
if not tcp_client_receiver.proc(socket_client) then
|
||||
log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
|
||||
break
|
||||
end
|
||||
|
||||
-- 数据发送处理
|
||||
-- 如果处理失败,则退出循环
|
||||
if not tcp_client_sender.proc(TASK_NAME, socket_client) then
|
||||
log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
|
||||
break
|
||||
end
|
||||
|
||||
-- 阻塞等待socket.EVENT事件或者15秒钟超时
|
||||
-- 以下三种业务逻辑会发布事件:
|
||||
-- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
|
||||
-- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
|
||||
-- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
|
||||
result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
|
||||
log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
|
||||
|
||||
-- 如果连接异常,则退出循环
|
||||
if not result then
|
||||
log.warn("tcp_client_main_task_func", "connection exception")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 出现异常
|
||||
::EXCEPTION_PROC::
|
||||
|
||||
-- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
|
||||
tcp_client_sender.exception_proc()
|
||||
|
||||
-- 如果存在socket client对象
|
||||
if socket_client then
|
||||
-- 关闭socket client连接
|
||||
libnet.close(TASK_NAME, 5000, socket_client)
|
||||
|
||||
-- 释放socket client对象
|
||||
socket.release(socket_client)
|
||||
socket_client = nil
|
||||
end
|
||||
|
||||
-- 5秒后跳转到循环体开始位置,自动发起重连
|
||||
sys.wait(5000)
|
||||
end
|
||||
end
|
||||
|
||||
--创建并且启动一个task
|
||||
--运行这个task的主函数tcp_client_main_task_func
|
||||
sys.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
--[[
|
||||
@module tcp_client_receiver
|
||||
@summary tcp client socket数据接收应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
|
||||
从内核读取接收到的数据,然后将数据处理后发送给其他应用功能模块做进一步处理;
|
||||
|
||||
本文件的对外接口有2个:
|
||||
1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
|
||||
2、sys.publish("RECV_DATA_FROM_SERVER", data):
|
||||
将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去处理;
|
||||
处理后通过消息"AIR_SRV_FOTA"发布到air_srv_fota.lua文件中去通知升级
|
||||
]]
|
||||
|
||||
local tcp_client_receiver = {}
|
||||
|
||||
-- socket数据接收缓冲区
|
||||
local recv_buff = nil
|
||||
|
||||
--[[
|
||||
检查socket client是否收到数据,如果收到数据,读取并且处理完所有数据
|
||||
|
||||
@api tcp_client_receiver.proc(socket_client)
|
||||
|
||||
@param1 socket_client userdata
|
||||
表示由socket.create接口创建的socket client对象;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@return1 result bool
|
||||
表示处理结果,成功为true,失败为false
|
||||
|
||||
@usage
|
||||
--
|
||||
tcp_client_receiver.proc(socket_client)
|
||||
]]
|
||||
function tcp_client_receiver.proc(socket_client)
|
||||
-- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
|
||||
if recv_buff == nil then
|
||||
recv_buff = zbuff.create(1024)
|
||||
-- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
|
||||
-- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
|
||||
-- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
|
||||
end
|
||||
|
||||
-- 循环从内核的缓冲区读取接收到的数据
|
||||
-- 如果读取失败,返回false,退出
|
||||
-- 如果读取成功,处理数据,并且继续循环读取
|
||||
-- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
|
||||
while true do
|
||||
-- 从内核的缓冲区中读取数据到recv_buff中
|
||||
-- 如果recv_buff的存储空间不足,会自动扩容
|
||||
local result = socket.rx(socket_client, recv_buff)
|
||||
|
||||
-- 读取数据失败
|
||||
-- 有两种情况:
|
||||
-- 1、recv_buff扩容失败
|
||||
-- 2、socket client和server之间的连接断开
|
||||
if not result then
|
||||
log.error("tcp_client_receiver.proc", "socket.rx error")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 如果读取到了数据, used()就必然大于0, 进行处理
|
||||
if recv_buff:used() > 0 then
|
||||
log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
|
||||
|
||||
-- 读取socket数据接收缓冲区中的数据,赋值给data
|
||||
local data = recv_buff:query()
|
||||
|
||||
-- 将数据data通过"RECV_IOT_DATA_SERVER"消息publish出去,给其他应用模块处理
|
||||
sys.publish("RECV_IOT_DATA_SERVER", data)
|
||||
|
||||
-- 清空socket数据接收缓冲区中的数据
|
||||
recv_buff:del()
|
||||
-- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--解析接受到的json参数并发布到消息"AIR_SRV_FOTA"中
|
||||
--定义一个json格式如下,具体可以根据实际情况定义,使用iot升级的话只定义了一个参数即可:
|
||||
--{
|
||||
-- "fota": "true"
|
||||
--}
|
||||
|
||||
local function data_processing(data)
|
||||
-- 解析json数据
|
||||
local json_data = json.decode(data)
|
||||
-- 如果解析失败
|
||||
if not json_data then
|
||||
log.error("data_processing", "json.decode error")
|
||||
return
|
||||
end
|
||||
-- 如果fota为true
|
||||
if json_data.fota == "true" then
|
||||
-- 发布消息"AIR_SRV_FOTA"
|
||||
sys.publish("AIR_SRV_FOTA")
|
||||
end
|
||||
end
|
||||
|
||||
-- 订阅"RECV_IOT_DATA_SERVER"消息
|
||||
sys.subscribe("RECV_IOT_DATA_SERVER", data_processing)
|
||||
|
||||
return tcp_client_receiver
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
--[[
|
||||
@module tcp_client_sender
|
||||
@summary tcp client socket数据发送应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.07.31
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
|
||||
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列iot_send_queue中;
|
||||
2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列iot_send_queue,逐条发送数据到server;
|
||||
3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列iot_send_queue中未发送的数据;
|
||||
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
|
||||
|
||||
本文件的对外接口有3个:
|
||||
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
|
||||
其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
|
||||
本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
|
||||
2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
|
||||
3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
|
||||
]]
|
||||
|
||||
local tcp_client_sender = {}
|
||||
|
||||
local libnet = require "libnet"
|
||||
|
||||
--[[
|
||||
数据发送队列,数据结构为:
|
||||
{
|
||||
[1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
|
||||
[2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
|
||||
}
|
||||
data的内容为真正要发送的数据,必须存在;
|
||||
func的内容为数据发送结果的用户回调函数,可以不存在
|
||||
para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
|
||||
]]
|
||||
local iot_send_queue = {}
|
||||
|
||||
-- tcp_client_main的任务名
|
||||
tcp_client_sender.TASK_NAME = "iot_tcp_client_main"
|
||||
|
||||
-- "SEND_DATA_REQ"消息的处理函数
|
||||
local function send_data_req_proc_func(data, cb)
|
||||
-- 将原始数据增加前缀,然后插入到发送队列iot_send_queue中
|
||||
table.insert(iot_send_queue, { data = data, cb = cb })
|
||||
-- 通知tcp_client_main主任务有数据需要发送
|
||||
-- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
|
||||
sys.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
|
||||
end
|
||||
|
||||
--[[
|
||||
检查socket client是否需要发送数据,如果需要发送数据,读取并且发送完发送队列中的所有数据
|
||||
|
||||
@api tcp_client_sender.proc(task_name, socket_client)
|
||||
|
||||
@param1 task_name string
|
||||
表示socket.create接口创建socket client对象时所处的task的name;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@param2 socket_client userdata
|
||||
表示由socket.create接口创建的socket client对象;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@return1 result bool
|
||||
表示处理结果,成功为true,失败为false
|
||||
|
||||
@usage
|
||||
tcp_client_sender.proc("tcp_client_main", socket_client)
|
||||
]]
|
||||
function tcp_client_sender.proc(task_name, socket_client)
|
||||
local send_item
|
||||
local result, buff_full
|
||||
|
||||
-- 遍历数据发送队列iot_send_queue
|
||||
while #iot_send_queue > 0 do
|
||||
-- 取出来第一条数据赋值给send_item
|
||||
-- 同时从队列iot_send_queue中删除这一条数据
|
||||
send_item = table.remove(iot_send_queue, 1)
|
||||
|
||||
-- 发送这条数据,超时时间15秒钟
|
||||
result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
|
||||
|
||||
-- 发送失败
|
||||
if not result then
|
||||
log.error("tcp_client_sender.proc", "libnet.tx error")
|
||||
|
||||
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(false, send_item.cb.para)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- 如果内核固件中缓冲区满了,则将send_item再次插入到iot_send_queue的队首位置,等待下次尝试发送
|
||||
if buff_full then
|
||||
log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
|
||||
table.insert(iot_send_queue, 1, send_item)
|
||||
return true
|
||||
end
|
||||
|
||||
log.info("tcp_client_sender.proc", "send success")
|
||||
-- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(true, send_item.cb.para)
|
||||
end
|
||||
|
||||
-- 发送成功,通知网络环境检测看门狗功能模块进行喂狗
|
||||
sys.publish("FEED_NETWORK_WATCHDOG")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
socket client连接出现异常时,清空等待发送的数据,并且执行发送方的回调函数
|
||||
|
||||
@api tcp_client_sender.exception_proc()
|
||||
|
||||
@usage
|
||||
tcp_client_sender.exception_proc()
|
||||
]]
|
||||
function tcp_client_sender.exception_proc()
|
||||
-- 遍历数据发送队列iot_send_queue
|
||||
while #iot_send_queue > 0 do
|
||||
local send_item = table.remove(iot_send_queue, 1)
|
||||
-- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(false, send_item.cb.para)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 订阅"SEND_DATA_REQ"消息;
|
||||
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
|
||||
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
|
||||
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
|
||||
|
||||
return tcp_client_sender
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
--[[
|
||||
@module update
|
||||
@summary 远程升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
实现远程升级功能,具体流程如下:
|
||||
1、判断网卡是否连接成功;
|
||||
2、初始化fota2模块;
|
||||
3、配置fota2模块的参数;
|
||||
4、调用fota2模块的升级函数;
|
||||
5、在升级结果的回调函数中,根据升级结果进行处理;
|
||||
]]
|
||||
-- 使用合宙iot平台时需要这个参数
|
||||
PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
|
||||
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
|
||||
|
||||
-- 循环打印版本号, 方便看版本号变化, 非必须
|
||||
function get_version()
|
||||
log.info("降功耗 找合宙")
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
end
|
||||
sys.timerLoopStart(get_version, 3000)
|
||||
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
|
||||
"3) 已经是最新版本,无需升级" )
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
-- 使用合宙iot平台进行升级, 支持自定义参数, 也可以不配置,如果要配置参数可以参考此链接https://docs.openluat.com/osapi/ext/libfota2/
|
||||
local ota_opts = {}
|
||||
|
||||
function fota_task_func()
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
|
||||
-- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
|
||||
if "123" == _G.PRODUCT_KEY then
|
||||
while true do
|
||||
sys.wait(1000)
|
||||
log.info("fota", "请修改正确的PRODUCT_KEY")
|
||||
end
|
||||
end
|
||||
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, ota_opts)
|
||||
end
|
||||
|
||||
--创建并且启动一个task
|
||||
--运行这个task的主函数fota_task_func
|
||||
sys.taskInit(fota_task_func)
|
||||
-- 演示定时自动升级, 每隔4小时自动检查一次
|
||||
sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, ota_opts)
|
||||
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
## 功能模块介绍
|
||||
|
||||
### 自建服务器fota功能演示
|
||||
|
||||
1、main.lua:主程序入口;
|
||||
|
||||
2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
|
||||
|
||||
3、update.lua:使用自建服务器进行远程升级功能模块,简单升级演示;
|
||||
|
||||
4、tcp_self_server文件夹:通过tcp服务器下发升级指令(指令格式使用json字符串,包含是否升级参数),控制设备启动customer_srv_fota功能模块,使用自建服务器进行升级;
|
||||
|
||||
5、customer_srv_fota.lua:自建服务器升级功能模块;
|
||||
|
||||
6、psm_power_fota.lua:低功耗fota功能模块,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子。需要注意的是此场景与上面两种场景不能同时使用;
|
||||
|
||||
|
||||
## 系统消息介绍
|
||||
|
||||
1、"IP_READY":某种网卡已经获取到ip信息,仅仅获取到了ip信息,能否和外网连通还不确认;
|
||||
|
||||
2、"IP_LOSE":某种网卡已经掉网;
|
||||
|
||||
|
||||
|
||||
## 用户消息介绍
|
||||
|
||||
1、"RECV_DATA_FROM_SERVER":socket client收到服务器下发的数据后,通过此消息发布出去,给其他应用模块处理;
|
||||
|
||||
2、"SEND_DATA_REQ":其他应用模块发布此消息,通知socket client发送数据给服务器;
|
||||
|
||||
|
||||
|
||||
## 演示功能概述
|
||||
|
||||
1、combination文件夹下的demo会有三个演示场景,在main.lua中选择要使用的场景:
|
||||
|
||||
(1) 使用自建服务器升级,演示最简单的升级逻辑。
|
||||
|
||||
(2) 使用自建服务器升级,通过tcp下发升级指令控制设备升级,指令格式使用json字符串,包含版本、url、是否升级参数,演示如何通过服务器控制下发指令去升级。
|
||||
|
||||
(3) 休眠状态下升级,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子。
|
||||
|
||||
2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
|
||||
|
||||
(1) netdrv_4g:4G网卡
|
||||
|
||||
(2) netdrv_wifi:WIFI STA网卡
|
||||
|
||||
(3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
|
||||
|
||||
(4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
|
||||
|
||||
|
||||
## 演示硬件环境
|
||||
|
||||

|
||||
|
||||
1、Air780EPM V1.3版本开发板一块+可上网的sim卡一张+4g天线一根+网线一根:
|
||||
|
||||
- sim卡插入开发板的sim卡槽
|
||||
|
||||
- 天线装到开发板上
|
||||
|
||||
- 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
|
||||
|
||||
2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EPM V1.3版本开发板和数据线的硬件接线方式为:
|
||||
|
||||
- Air780EPM V1.3版本开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
|
||||
|
||||
- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
|
||||
|
||||
|
||||
|
||||
## 演示软件环境
|
||||
|
||||
1、Luatools下载调试工具
|
||||
|
||||
2、[Air780EPM V2012版本固件)](https://docs.openluat.com/air780epm/luatos/firmware/version/)
|
||||
|
||||
3、PC端浏览器访问[合宙TCP/UDP web测试工具](https://iot.luatos.com/#/page6/netlab);
|
||||
|
||||
|
||||
## 演示核心步骤
|
||||
|
||||
1、搭建好硬件环境
|
||||
|
||||
2、PC端浏览器访问[合宙TCP/UDP web测试工具](https://iot.luatos.com/#/page6/netlab),点击 打开TCP 按钮,会创建一个TCP server,将server的地址和端口赋值给tcp_client_self_main.lua中的SERVER_ADDR和SERVER_PORT两个变量
|
||||
|
||||
详细使用说明参考:[合宙 TCP/UDP web 测试工具使用说明](https://iot.luatos.com/#/p8000/netlab) 。
|
||||
|
||||
3、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
|
||||
|
||||
- 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
|
||||
|
||||
- 如果需要单WIFI STA网卡,打开require "netdrv_wifi",其余注释掉;同时netdrv_wifi.lua中的wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
|
||||
|
||||
- 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
|
||||
|
||||
- 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
|
||||
|
||||
4、Luatools烧录内核固件和修改后的demo脚本代码
|
||||
|
||||
5、使用Luatools制作升级包,先把新旧版本分别生成量产文件,然后再制作升级包,工具上栏 luatOS->固件工具->差分包/整包升级包制作,将制作好的升级包配置到合宙iot服务器自己项目下,或上传到自建服务器上面;
|
||||
|
||||
6、烧录成功后,自动开机运行
|
||||
|
||||
7、[合宙TCP/UDP web测试工具](https://iot.luatos.com/#/page6/netlab)上创建的两个TCP server可以看到有设备连接上来,然后可以下发下面字符串触发升级:
|
||||
|
||||
|
||||
``` lua
|
||||
|
||||
--自建服务器下发这个指令,下发之前需要在服务器上面配置好升级包,然后吧url给到字符串
|
||||
--定义一个json格式如下,具体可以根据实际情况定义:
|
||||
-- {"fota": "true","url": "http://airtest.openluat.com:2900/download/FOTA2_DEMO_2012.001.001_LuatOS-SoC_Air780EPM.bin"}
|
||||
|
||||
```
|
||||
|
||||
8、可以看到升级过程如下,不管是什么场景下升级,基本都是如下情况:
|
||||
|
||||
``` lua
|
||||
--没有升级之前可以看到如下打印
|
||||
I/user.fota 脚本版本号 001.000.000 core版本号 V2010
|
||||
|
||||
I/user.fota_task_func recv IP_READY 1
|
||||
I/user.开始检查升级
|
||||
|
||||
I/user.升级包下载成功,重启模块
|
||||
|
||||
|
||||
--升级之后可以看到如下打印
|
||||
I/user.fota 脚本版本号 001.000.001 core版本号 V2012
|
||||
--升级重启之后还是会检查升级,所以会有如下打印属于正常情况,其中"code": 27 是合宙iot服务器返回的状态码,意思是已经是最新版本了。自建服务器如果没做设置会再下载一遍升级包。
|
||||
|
||||
I/user.fota -9 {"code": 27, "msg": "\u5df2\u662f\u6700\u65b0\u7248\u672c"}
|
||||
I/user.使用合宙服务器,接下来解析body里的code
|
||||
I/user.已是最新版本 1.设备的固件/脚本版本高于或等于云平台上的版本号 2.用户项目升级配置中未添加该设备 3.云平台升级配置中,是否升级配置为否
|
||||
I/user.fota 4
|
||||
|
||||
|
||||
```
|
||||
|
||||
9、对于psm休眠状态下的升级的场景,打印的版本来判断是否是最新版本。模组升级成功后会自动进入psm休眠状态。可以通过电流查看休眠情况。
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
--[[
|
||||
@module air_srv_fota
|
||||
@summary 使用自建服务器远程升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
实现远程升级功能,具体流程如下:
|
||||
1、接收 CUSTOMER_SRV_FOTA 系统消息,触发升级;
|
||||
2、判断网卡是否连接成功;
|
||||
3、初始化fota2模块;
|
||||
4、调用fota2模块的升级函数;
|
||||
5、根据升级结果进行处理;
|
||||
]]
|
||||
|
||||
|
||||
--加在libfota2扩展库
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
-- 循环打印版本号, 方便看版本号变化, 非必须
|
||||
function get_version()
|
||||
log.info("降功耗 找合宙")
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
end
|
||||
|
||||
sys.timerLoopStart(get_version, 3000)
|
||||
|
||||
-- fota升级标志:true 表示当前正有 FOTA 流程在跑
|
||||
local fota_running = false
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
-- fota结束,无论成功还是失败,都释放fota_running标志
|
||||
fota_running = false
|
||||
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
|
||||
"3) 已经是最新版本,无需升级")
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 使用第三方服务器,配置ota_opts参数
|
||||
--[[
|
||||
-- opts参数说明, 所有参数都是可选的
|
||||
-- 1. opts.url string 升级所需要的URL, 若使用合宙iot平台,则不需要填
|
||||
-- 2. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 3. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 4. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 5. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 6. opts.firmware_name string 固件名称,默认是 _G.PROJECT.. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
-- 7. opts.server_cert string 服务器证书, 默认不使用
|
||||
-- 8. opts.client_cert string 客户端证书, 默认不使用
|
||||
-- 9. opts.client_key string 客户端私钥, 默认不使用
|
||||
-- 10. opts.client_password string 客户端私钥口令, 默认不使用
|
||||
-- 11. opts.method string 请求方法, 默认是GET
|
||||
-- 12. opts.headers table 额外添加的请求头,默认不需要
|
||||
-- 13. opts.body string 额外添加的请求body,默认不需要
|
||||
]]
|
||||
local opts = {
|
||||
url = "",
|
||||
-- 合宙IOT平台的默认升级URL, 不填就是这个默认值
|
||||
-- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
|
||||
-- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
|
||||
-- 如果不加###,则默认会上传如下参数
|
||||
-- 1. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 2. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 3. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 4. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 5. opts.firmware_name string 底层版本号
|
||||
|
||||
-- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
|
||||
version = ""
|
||||
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
|
||||
}
|
||||
|
||||
|
||||
local function air_fota_func(data)
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
while true do
|
||||
-- 阻塞等待外部事件:"CUSTOMER_SRV_FOTA"
|
||||
local result, data = sys.waitUntil("CUSTOMER_SRV_FOTA")
|
||||
if result then
|
||||
log.info("接收到数据", "date", #data)
|
||||
|
||||
if fota_running then
|
||||
log.warn("fota_task", "FOTA 正在运行,跳过本次请求")
|
||||
else
|
||||
-- 标记FOTA正在运行
|
||||
-- 注意:这里只是标记,实际的FOTA流程还没有开始
|
||||
opts.url = data.url
|
||||
opts.version = data.version
|
||||
fota_running = true
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 初始化FOTA任务
|
||||
sys.taskInit(air_fota_func)
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
--[[
|
||||
@module main
|
||||
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本demo演示的核心功能为:
|
||||
1、这个demo会有三个演示场景:
|
||||
(1)使用自建服务器升级,演示最简单的升级逻辑。
|
||||
(2)使用自建服务器升级,通过tcp下发升级指令控制设备升级,指令格式使用json字符串,包含版本、url、是否升级参数,演示如何通过服务器控制下发指令去升级。
|
||||
(3)休眠状态下升级,此场景是针对psm状态下升级没完成就进入休眠导致升级失败的情况写的一个例子。
|
||||
2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
|
||||
(1) netdrv_4g:4G网卡
|
||||
(2) netdrv_wifi:WIFI STA网卡
|
||||
(3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
|
||||
(4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
|
||||
|
||||
更多说明参考本目录下的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这三位数字必须存在,但是没有任何用处,可以一直写为999
|
||||
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
|
||||
]]
|
||||
PROJECT = "FOTA2_DEMO"
|
||||
VERSION = "001.999.000"
|
||||
|
||||
|
||||
-- 在日志中打印项目名和项目版本号
|
||||
log.info("main", PROJECT, VERSION)
|
||||
|
||||
|
||||
|
||||
|
||||
-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
|
||||
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
|
||||
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
|
||||
-- 启动errDump日志存储并且上传功能,600秒上传一次
|
||||
-- if errDump then
|
||||
-- errDump.config(true, 600)
|
||||
-- end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- 启动一个循环定时器
|
||||
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
|
||||
-- 方便分析内存使用是否有异常
|
||||
-- sys.timerLoopStart(function()
|
||||
-- log.info("mem.lua", rtos.meminfo())
|
||||
-- log.info("mem.sys", rtos.meminfo("sys"))
|
||||
-- end, 3000)
|
||||
|
||||
-- 循环打印版本号, 方便看版本号变化, 非必须
|
||||
function get_version()
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
end
|
||||
|
||||
sys.timerLoopStart(get_version, 3000)
|
||||
|
||||
|
||||
-- 加载网络驱动设备功能模块
|
||||
require "netdrv_device"
|
||||
|
||||
|
||||
|
||||
--两种tcp下发指令升级的场景和psm+低功耗模式升级不能同时使用,需要根据自己场景选择其中一种
|
||||
|
||||
--两种tcp下发指令升级的场景可以启用一种,也可以启用两种,启用两种时,注意控制不要一个在fota的过程中,另外一个再fota。
|
||||
|
||||
|
||||
-- 加载远程升级功能模块,场景一
|
||||
require "update"
|
||||
---------------------------------------------------------------------------
|
||||
-- 加载tcp client self socket主应用功能模块,通过tcp下发升级指令控制设备升级,指令格式使用json字符串,包含版本、url、是否升级参数,演示如何通过服务器控制下发指令去升级。场景二
|
||||
-- require "tcp_self_main"
|
||||
-- 加载自建服务器远程升级功能模块
|
||||
-- require "customer_srv_fota"
|
||||
---------------------------------------------------------------------------
|
||||
-- 加载psm+低功耗模式升级功能模块,场景三
|
||||
-- require "psm_power_fota"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- 用户代码已结束---------------------------------------------
|
||||
-- 结尾总是这一句
|
||||
sys.run()
|
||||
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
@module netdrv_4g
|
||||
@summary “4G网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.01
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为4G网卡驱动模块,核心业务逻辑为:
|
||||
1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
|
||||
]]
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.LWIP_GP then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_4g.ip_ready_func", "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_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
|
||||
-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
-- 在Air780EPM上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
--[[
|
||||
@module netdrv_eth_spi
|
||||
@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.24
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
|
||||
1、打开CH390H芯片供电开关;
|
||||
2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
|
||||
3、以太网卡的连接状态发生变化时,在日志中进行打印;
|
||||
|
||||
直接使用Air780EPM V1.3版本开发板硬件测试即可;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
|
||||
]]
|
||||
|
||||
local exnetif = require "exnetif"
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.LWIP_ETH then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
|
||||
end
|
||||
end
|
||||
|
||||
local function ip_lose_func(adapter)
|
||||
if adapter == socket.LWIP_ETH then
|
||||
log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
|
||||
-- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
|
||||
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
|
||||
|
||||
-- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
|
||||
-- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
|
||||
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
|
||||
|
||||
--此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
|
||||
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
|
||||
local function netdrv_eth_spi_task_func()
|
||||
-- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
|
||||
-- 本demo使用Air780EPM V1.3版本开发板测试,开发板上的硬件配置为:
|
||||
-- GPIO20为CH390H以太网芯片的供电使能控制引脚
|
||||
-- 使用spi0,片选引脚使用GPIO8,中断引脚使用GPIO22
|
||||
-- 如果使用的硬件和以上描述的环境不同,根据自己的硬件配置修改以下参数
|
||||
exnetif.set_priority_order({
|
||||
{
|
||||
ETHERNET = {
|
||||
pwrpin = 20,
|
||||
tp = netdrv.CH390,
|
||||
opts = {spi = 0, cs = 8, irq = 22}
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
-- 启动一个task,task的处理函数为netdrv_eth_spi_task_func
|
||||
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
|
||||
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
|
||||
sys.taskInit(netdrv_eth_spi_task_func)
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
--[[
|
||||
@module netdrv_multiple
|
||||
@summary 多网卡(4G网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.24
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件为多网卡驱动模块,核心业务逻辑为:
|
||||
1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
|
||||
|
||||
直接使用Air780EPM V1.3版本开发板硬件测试即可;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
|
||||
]]
|
||||
|
||||
local exnetif = require "exnetif"
|
||||
|
||||
-- 网卡状态变化通知回调函数
|
||||
-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
|
||||
-- 当网卡切换切换时:
|
||||
-- net_type:string类型,表示当前使用的网卡字符串
|
||||
-- adapter:number类型,表示当前使用的网卡id
|
||||
-- 当所有网卡断网时:
|
||||
-- net_type:为nil
|
||||
-- adapter:number类型,为-1
|
||||
local function netdrv_multiple_notify_cbfunc(net_type,adapter)
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
if type(net_type)=="string" then
|
||||
log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
|
||||
elseif type(net_type)=="nil" then
|
||||
log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
|
||||
else
|
||||
log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
|
||||
end
|
||||
end
|
||||
|
||||
local function netdrv_multiple_task_func()
|
||||
--设置网卡优先级
|
||||
exnetif.set_priority_order(
|
||||
{
|
||||
-- “通过SPI外挂CH390H芯片”的以太网卡,使用Air780EPM V1.3版本开发板验证
|
||||
{
|
||||
ETHERNET = {
|
||||
-- 供电使能GPIO
|
||||
pwrpin = 20,
|
||||
-- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
|
||||
-- 如果没有传入此参数,exnetif会使用默认值10秒
|
||||
ping_time = 3000,
|
||||
|
||||
-- 连通性检测ip(选填参数);
|
||||
-- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
|
||||
-- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
|
||||
-- ping_ip = "填入可靠的并且可以ping通的ip地址",
|
||||
|
||||
-- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
|
||||
tp = netdrv.CH390,
|
||||
opts = {spi=0, cs=8, irq=22}
|
||||
}
|
||||
},
|
||||
|
||||
-- 4G网卡
|
||||
{
|
||||
LWIP_GP = true
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
|
||||
exnetif.notify_status(netdrv_multiple_notify_cbfunc)
|
||||
|
||||
-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
|
||||
-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
|
||||
-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
|
||||
|
||||
|
||||
-- 启动一个task,task的处理函数为netdrv_multiple_task_func
|
||||
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
|
||||
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
|
||||
sys.taskInit(netdrv_multiple_task_func)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
--[[
|
||||
@module netdrv_pc
|
||||
@summary “pc模拟器网卡”驱动模块
|
||||
@version 1.0
|
||||
@date 2025.07.01
|
||||
@author 朱天华
|
||||
@usage
|
||||
本文件为pc模拟器网卡驱动模块,核心业务逻辑为:
|
||||
1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
|
||||
|
||||
本文件没有对外接口,直接在其他功能模块中require "netdrv_pc"就可以加载运行;
|
||||
]]
|
||||
|
||||
local function ip_ready_func(ip, adapter)
|
||||
if adapter == socket.ETH0 then
|
||||
-- 在位置1和2设置自定义的DNS服务器ip地址:
|
||||
-- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
|
||||
-- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
|
||||
-- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
|
||||
-- 如果使用专网卡,不要使用这两行代码;
|
||||
-- 如果使用国外的网络,不要使用这两行代码;
|
||||
socket.setDNS(adapter, 1, "223.5.5.5")
|
||||
socket.setDNS(adapter, 2, "114.114.114.114")
|
||||
|
||||
log.info("netdrv_pc.ip_ready_func", "IP_READY", socket.localIP(socket.ETH0))
|
||||
end
|
||||
end
|
||||
|
||||
local function ip_lose_func(adapter)
|
||||
if adapter == socket.ETH0 then
|
||||
log.warn("netdrv_pc.ip_lose_func", "IP_LOSE")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--此处订阅"IP_READY"和"IP_LOSE"两种消息
|
||||
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察pc模拟器网络的连接状态
|
||||
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
|
||||
sys.subscribe("IP_READY", ip_ready_func)
|
||||
sys.subscribe("IP_LOSE", ip_lose_func)
|
||||
|
||||
-- 设置默认网卡为socket.ETH0
|
||||
-- pc模拟器上的默认网卡仍然需要使用接口(socket.ETH0)来设置,因为exnetif扩展库当前还不支持模拟器
|
||||
socket.dft(socket.ETH0)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
--[[
|
||||
@module netdrv_device
|
||||
@summary 网络驱动设备功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
|
||||
1、netdrv_4g:socket.LWIP_GP,4G网卡;
|
||||
2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
|
||||
3、netdrv_eth_spi:socket.LWIP_USER1,通过SPI外挂CH390H芯片的以太网卡;
|
||||
4、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
|
||||
|
||||
根据自己的项目需求,只需要require以上四种中的一种即可;
|
||||
|
||||
|
||||
本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
|
||||
]]
|
||||
|
||||
|
||||
-- 根据自己的项目需求,只需要require以下四种中的一种即可;
|
||||
|
||||
-- 加载“4G网卡”驱动模块
|
||||
require "netdrv_4g"
|
||||
|
||||
-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
|
||||
-- require "netdrv_eth_spi"
|
||||
|
||||
-- 加载“可以配置优先级的多种网卡”驱动模块
|
||||
-- require "netdrv_multiple"
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
--[[
|
||||
@module psm+_power_fota
|
||||
@summary psm+超低功耗模式下升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为psm+超低功耗模式下升级功能模块,核心设计思路
|
||||
1.升级触发机制 :
|
||||
- 定时器唤醒升级 :设备定期从PSM模式唤醒,主动检查是否有新固件版本。
|
||||
- 外部中断唤醒升级 :通过特定GPIO中断或网络消息唤醒设备进行升级。
|
||||
2.防止升级过程中进入休眠 :
|
||||
- 在开始FOTA升级前,禁用PSM模式进入。
|
||||
- 升级完成后,根据结果决定是否重启设备或重新进入PSM模式。
|
||||
|
||||
]]
|
||||
-- 使用合宙iot平台时需要这个参数
|
||||
PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
|
||||
--加在libfota2扩展库
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
--升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
|
||||
sys.publish("FOTA_END")
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
|
||||
"3) 已经是最新版本,无需升级" )
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- 使用合宙iot平台进行升级, 支持自定义参数, 也可以不配置,如果要配置参数可以参考此链接https://docs.openluat.com/osapi/ext/libfota2/
|
||||
local opts = {
|
||||
-- 使用的是自定义的URL,此处写的URL是不存在的一个无效URL,自己测试时,需要替换为自己的有效URL
|
||||
url = "###http://your_fota_packet_url.bin",
|
||||
-- 合宙IOT平台的默认升级URL, 不填就是这个默认值
|
||||
-- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
|
||||
-- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
|
||||
-- 如果不加###,则默认会上传如下参数
|
||||
-- 1. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 2. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 3. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 4. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 5. opts.firmware_name string 底层版本号
|
||||
|
||||
-- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
|
||||
-- version = ""
|
||||
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
|
||||
}
|
||||
|
||||
|
||||
function psm_fota_task_func()
|
||||
-- 如果是被定时器唤醒,因为上次进入PSM+时是开启了飞行模式,所以在唤醒后第一时间关闭飞行模式。
|
||||
mobile.flymode(0, false)
|
||||
log.info("开始测试PSM+模式功耗。")
|
||||
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, opts)
|
||||
|
||||
-- 打印版本号, 方便看版本号变化, 非必须
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
|
||||
|
||||
-- 等待下载升级包结束, 发布消息"FOTA_END",
|
||||
-- 如果15秒内没有收到消息,则15秒的时长到达后进入PSM+模式。
|
||||
-- 需要注意的是在fota_cb回调函数中,升级包下载成功后,会立马重启并升级模组。如果还有其他事情要做不想立马重启升级,需自行决定reboot的时机
|
||||
-- 升级包下载成功后,本demo默认是立即自动重启并且将升级包更新到模组中,更新成功后,会再次走到这里
|
||||
-- 再次走到这里后,合宙iot平台会返回“已经是最新版本,不需要升级”,fota_cb回调函数中会发布消息"FOTA_END"
|
||||
-- 至此,才会继续向下执行代码,进入PSM+模式
|
||||
sys.waitUntil("FOTA_END", 15000)
|
||||
|
||||
log.info("升级结束,进入PSM模式")
|
||||
|
||||
|
||||
-- 定时检查升级 (每4小时唤醒一次)
|
||||
pm.dtimerStart(2, 4 * 3600000)
|
||||
-- 启动飞行模式,规避可能会出现的网络问题
|
||||
mobile.flymode(0, true)
|
||||
-- 进入PSM模式
|
||||
pm.power(pm.WORK_MODE, 3)
|
||||
-- 防御机制:15秒后如果未进入PSM则重启
|
||||
sys.wait(15000)
|
||||
log.info("进入PSM+失败,重启")
|
||||
rtos.reboot()
|
||||
end
|
||||
|
||||
sys.taskInit(psm_fota_task_func)
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
--[[
|
||||
@module tcp_client_main
|
||||
@summary tcp client socket主应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket主应用功能模块,核心业务逻辑为:
|
||||
1、创建一个tcp client socket,连接server;
|
||||
2、处理连接异常,出现异常后执行重连动作;
|
||||
3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
|
||||
|
||||
本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
|
||||
]]
|
||||
|
||||
local libnet = require "libnet"
|
||||
|
||||
-- 加载tcp client socket数据接收功能模块
|
||||
local tcp_client_receiver = require "tcp_self_receiver"
|
||||
-- 加载tcp client socket数据发送功能模块
|
||||
local tcp_client_sender = require "tcp_self_sender"
|
||||
|
||||
-- 电脑访问:https://iot.luatos.com/#/page6/netlab
|
||||
-- 本工具使用方法可以参考:https://docs.openluat.com/common/TCPUDP_Test/
|
||||
-- 点击 打开TCP 按钮,会创建一个TCP server
|
||||
-- 将server的地址和端口赋值给下面这两个变量
|
||||
local SERVER_ADDR = "112.125.89.8"
|
||||
local SERVER_PORT = 45433
|
||||
|
||||
-- tcp_client_main的任务名
|
||||
local TASK_NAME = tcp_client_sender.TASK_NAME
|
||||
|
||||
|
||||
-- 处理未识别的消息
|
||||
local function tcp_client_main_cbfunc(msg)
|
||||
log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
|
||||
end
|
||||
|
||||
-- tcp client socket的任务处理函数
|
||||
local function tcp_client_main_task_func()
|
||||
local socket_client
|
||||
local result, para1, para2
|
||||
|
||||
while true do
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("sntp_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("tcp_client_main_task_func", "recv IP_READY", socket.dft())
|
||||
|
||||
-- 创建socket client对象
|
||||
socket_client = socket.create(nil, TASK_NAME)
|
||||
-- 如果创建socket client对象失败
|
||||
if not socket_client then
|
||||
log.error("tcp_client_main_task_func", "socket.create error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
-- 配置socket client对象为tcp client
|
||||
result = socket.config(socket_client, nil, nil, nil, 300, 10, 3)
|
||||
-- 如果配置失败
|
||||
if not result then
|
||||
log.error("tcp_client_main_task_func", "socket.config error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
-- 连接server
|
||||
result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
|
||||
-- 如果连接server失败
|
||||
if not result then
|
||||
log.error("tcp_client_main_task_func", "libnet.connect error")
|
||||
goto EXCEPTION_PROC
|
||||
end
|
||||
|
||||
log.info("tcp_client_main_task_func", "libnet.connect success")
|
||||
|
||||
-- 数据收发以及网络连接异常事件总处理逻辑
|
||||
while true do
|
||||
-- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
|
||||
-- 如果处理失败,则退出循环
|
||||
if not tcp_client_receiver.proc(socket_client) then
|
||||
log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
|
||||
break
|
||||
end
|
||||
|
||||
-- 数据发送处理
|
||||
-- 如果处理失败,则退出循环
|
||||
if not tcp_client_sender.proc(TASK_NAME, socket_client) then
|
||||
log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
|
||||
break
|
||||
end
|
||||
|
||||
-- 阻塞等待socket.EVENT事件或者15秒钟超时
|
||||
-- 以下三种业务逻辑会发布事件:
|
||||
-- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
|
||||
-- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
|
||||
-- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
|
||||
result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
|
||||
log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
|
||||
|
||||
-- 如果连接异常,则退出循环
|
||||
if not result then
|
||||
log.warn("tcp_client_main_task_func", "connection exception")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 出现异常
|
||||
::EXCEPTION_PROC::
|
||||
|
||||
-- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
|
||||
tcp_client_sender.exception_proc()
|
||||
|
||||
-- 如果存在socket client对象
|
||||
if socket_client then
|
||||
-- 关闭socket client连接
|
||||
libnet.close(TASK_NAME, 5000, socket_client)
|
||||
|
||||
-- 释放socket client对象
|
||||
socket.release(socket_client)
|
||||
socket_client = nil
|
||||
end
|
||||
|
||||
-- 5秒后跳转到循环体开始位置,自动发起重连
|
||||
sys.wait(5000)
|
||||
end
|
||||
end
|
||||
|
||||
--创建并且启动一个task
|
||||
--运行这个task的主函数tcp_client_main_task_func
|
||||
sys.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
--[[
|
||||
@module tcp_client_receiver
|
||||
@summary tcp client socket数据接收应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
|
||||
从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;
|
||||
|
||||
本文件的对外接口有2个:
|
||||
1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
|
||||
2、sys.publish("RECV_DATA_FROM_SERVER", data):数据接收应用逻辑处理入口,在tcp_client_main
|
||||
将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去处理;
|
||||
处理后通过消息"CUSTOMER_SRV_FOTA"发布到customer_srv_fota.lua文件中去通知升级
|
||||
]]
|
||||
|
||||
local tcp_client_receiver = {}
|
||||
|
||||
-- socket数据接收缓冲区
|
||||
local recv_buff = nil
|
||||
|
||||
--[[
|
||||
检查socket client是否收到数据,如果收到数据,读取并且处理完所有数据
|
||||
|
||||
@api tcp_client_receiver.proc(socket_client)
|
||||
|
||||
@param1 socket_client userdata
|
||||
表示由socket.create接口创建的socket client对象;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@return1 result bool
|
||||
表示处理结果,成功为true,失败为false
|
||||
|
||||
@usage
|
||||
--
|
||||
tcp_client_receiver.proc(socket_client)
|
||||
]]
|
||||
function tcp_client_receiver.proc(socket_client)
|
||||
-- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
|
||||
if recv_buff==nil then
|
||||
recv_buff = zbuff.create(1024)
|
||||
-- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
|
||||
-- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
|
||||
-- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
|
||||
end
|
||||
|
||||
-- 循环从内核的缓冲区读取接收到的数据
|
||||
-- 如果读取失败,返回false,退出
|
||||
-- 如果读取成功,处理数据,并且继续循环读取
|
||||
-- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
|
||||
while true do
|
||||
-- 从内核的缓冲区中读取数据到recv_buff中
|
||||
-- 如果recv_buff的存储空间不足,会自动扩容
|
||||
local result = socket.rx(socket_client, recv_buff)
|
||||
|
||||
-- 读取数据失败
|
||||
-- 有两种情况:
|
||||
-- 1、recv_buff扩容失败
|
||||
-- 2、socket client和server之间的连接断开
|
||||
if not result then
|
||||
log.error("tcp_client_receiver.proc", "socket.rx error")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 如果读取到了数据, used()就必然大于0, 进行处理
|
||||
if recv_buff:used() > 0 then
|
||||
log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
|
||||
|
||||
-- 读取socket数据接收缓冲区中的数据,赋值给data
|
||||
local data = recv_buff:query()
|
||||
|
||||
-- 将数据data通过"RECV_SELF_DATA_SERVER"消息publish出去,给其他应用模块处理
|
||||
sys.publish("RECV_SELF_DATA_SERVER", data)
|
||||
|
||||
-- 清空socket数据接收缓冲区中的数据
|
||||
recv_buff:del()
|
||||
-- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
--解析接受到的json参数并发布到消息"AIR_SRV_FOTA"中
|
||||
--定义一个json格式如下,具体可以根据实际情况定义:
|
||||
--{
|
||||
-- "fota": "true",
|
||||
-- "url": "http://192.168.1.100:8080/firmware.bin"
|
||||
--}
|
||||
|
||||
local function data_processing(data)
|
||||
-- 解析json数据
|
||||
local json_data = json.decode(data)
|
||||
|
||||
-- 如果解析失败
|
||||
if not json_data then
|
||||
log.error("data_processing", "json.decode error")
|
||||
return
|
||||
end
|
||||
-- 解析fota参数
|
||||
local fota = json_data.fota
|
||||
-- 如果fota参数不存在
|
||||
if not fota then
|
||||
log.error("data_processing", "fota error")
|
||||
return
|
||||
end
|
||||
-- 解析url参数
|
||||
local url = json_data.url
|
||||
-- 如果url参数不存在
|
||||
if not url then
|
||||
log.error("data_processing", "url error")
|
||||
return
|
||||
end
|
||||
-- 如果fota为true
|
||||
if fota == "true" then
|
||||
-- -- 发布消息"CUSTOMER_SRV_FOTA"
|
||||
sys.publish("CUSTOMER_SRV_FOTA",json_data)
|
||||
end
|
||||
end
|
||||
|
||||
-- 订阅"RECV_SELF_DATA_SERVER"消息
|
||||
sys.subscribe("RECV_SELF_DATA_SERVER", data_processing)
|
||||
|
||||
return tcp_client_receiver
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
--[[
|
||||
@module tcp_client_sender
|
||||
@summary tcp client socket数据发送应用功能模块
|
||||
@version 1.0
|
||||
@date 2025.07.31
|
||||
@author 孟伟
|
||||
@usage
|
||||
本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
|
||||
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列self_send_queue中;
|
||||
2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列self_send_queue,逐条发送数据到server;
|
||||
3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列self_send_queue中未发送的数据;
|
||||
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
|
||||
|
||||
本文件的对外接口有3个:
|
||||
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
|
||||
其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
|
||||
本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
|
||||
2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
|
||||
3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
|
||||
]]
|
||||
|
||||
local tcp_client_sender = {}
|
||||
|
||||
local libnet = require "libnet"
|
||||
|
||||
--[[
|
||||
数据发送队列,数据结构为:
|
||||
{
|
||||
[1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
|
||||
[2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
|
||||
}
|
||||
data的内容为真正要发送的数据,必须存在;
|
||||
func的内容为数据发送结果的用户回调函数,可以不存在
|
||||
para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
|
||||
]]
|
||||
local self_send_queue = {}
|
||||
|
||||
-- tcp_client_main的任务名
|
||||
tcp_client_sender.TASK_NAME = "self_tcp_client_main"
|
||||
|
||||
-- "SEND_DATA_REQ"消息的处理函数
|
||||
local function send_data_req_proc_func(data, cb)
|
||||
-- 将原始数据增加前缀,然后插入到发送队列self_send_queue中
|
||||
table.insert(self_send_queue, {data=data, cb=cb})
|
||||
-- 通知tcp_client_main主任务有数据需要发送
|
||||
-- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
|
||||
sys.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
|
||||
end
|
||||
|
||||
--[[
|
||||
检查socket client是否需要发送数据,如果需要发送数据,读取并且发送完发送队列中的所有数据
|
||||
|
||||
@api tcp_client_sender.proc(task_name, socket_client)
|
||||
|
||||
@param1 task_name string
|
||||
表示socket.create接口创建socket client对象时所处的task的name;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@param2 socket_client userdata
|
||||
表示由socket.create接口创建的socket client对象;
|
||||
必须传入,不允许为空或者nil;
|
||||
|
||||
@return1 result bool
|
||||
表示处理结果,成功为true,失败为false
|
||||
|
||||
@usage
|
||||
tcp_client_sender.proc("tcp_client_main", socket_client)
|
||||
]]
|
||||
function tcp_client_sender.proc(task_name, socket_client)
|
||||
local send_item
|
||||
local result, buff_full
|
||||
-- 遍历数据发送队列self_send_queue
|
||||
while #self_send_queue>0 do
|
||||
-- 取出来第一条数据赋值给send_item
|
||||
-- 同时从队列self_send_queue中删除这一条数据
|
||||
send_item = table.remove(self_send_queue,1)
|
||||
|
||||
-- 发送这条数据,超时时间15秒钟
|
||||
result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
|
||||
|
||||
-- 发送失败
|
||||
if not result then
|
||||
log.error("tcp_client_sender.proc", "libnet.tx error")
|
||||
|
||||
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(false, send_item.cb.para)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- 如果内核固件中缓冲区满了,则将send_item再次插入到self_send_queue的队首位置,等待下次尝试发送
|
||||
if buff_full then
|
||||
log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
|
||||
table.insert(self_send_queue, 1, send_item)
|
||||
return true
|
||||
end
|
||||
|
||||
log.info("tcp_client_sender.proc", "send success")
|
||||
-- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(true, send_item.cb.para)
|
||||
end
|
||||
|
||||
-- 发送成功,通知网络环境检测看门狗功能模块进行喂狗
|
||||
sys.publish("FEED_NETWORK_WATCHDOG")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
socket client连接出现异常时,清空等待发送的数据,并且执行发送方的回调函数
|
||||
|
||||
@api tcp_client_sender.exception_proc()
|
||||
|
||||
@usage
|
||||
tcp_client_sender.exception_proc()
|
||||
]]
|
||||
function tcp_client_sender.exception_proc()
|
||||
-- 遍历数据发送队列self_send_queue
|
||||
while #self_send_queue>0 do
|
||||
local send_item = table.remove(self_send_queue,1)
|
||||
-- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
|
||||
if send_item.cb and send_item.cb.func then
|
||||
send_item.cb.func(false, send_item.cb.para)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 订阅"SEND_DATA_REQ"消息;
|
||||
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
|
||||
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
|
||||
sys.subscribe("SELF_SEND_DATA_REQ", send_data_req_proc_func)
|
||||
|
||||
return tcp_client_sender
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
--[[
|
||||
@module update
|
||||
@summary 远程升级功能模块
|
||||
@version 1.0
|
||||
@date 2025.08.12
|
||||
@author 孟伟
|
||||
@usage
|
||||
实现远程升级功能,具体流程如下:
|
||||
1、判断网卡是否连接成功;
|
||||
2、初始化fota2模块;
|
||||
3、配置fota2模块的参数;
|
||||
4、调用fota2模块的升级函数;
|
||||
5、在升级结果的回调函数中,根据升级结果进行处理;
|
||||
]]
|
||||
|
||||
libfota2 = require "libfota2"
|
||||
|
||||
|
||||
|
||||
-- 循环打印版本号, 方便看版本号变化, 非必须
|
||||
function get_version()
|
||||
log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
|
||||
end
|
||||
sys.timerLoopStart(get_version, 3000)
|
||||
|
||||
|
||||
-- 升级结果的回调函数
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
local function fota_cb(ret)
|
||||
log.info("fota", ret)
|
||||
if ret == 0 then
|
||||
log.info("升级包下载成功,重启模块")
|
||||
rtos.reboot()
|
||||
elseif ret == 1 then
|
||||
log.info("连接失败", "请检查url拼写或服务器配置(是否为内网)")
|
||||
elseif ret == 2 then
|
||||
log.info("url错误", "检查url拼写")
|
||||
elseif ret == 3 then
|
||||
log.info("服务器断开", "检查服务器白名单配置")
|
||||
elseif ret == 4 then
|
||||
log.error("FOTA 失败",
|
||||
"原因可能有:\n" ..
|
||||
"1) 服务器返回 200/206 但报文体为空(0 字节)—— 通常是升级包文件缺失或 URL 指向空文件;\n" ..
|
||||
"2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
|
||||
"3) 已经是最新版本,无需升级" )
|
||||
elseif ret == 5 then
|
||||
log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
|
||||
else
|
||||
log.info("不是上面几种情况 ret为", ret)
|
||||
end
|
||||
end
|
||||
|
||||
-- 使用第三方服务器,配置ota_opts参数
|
||||
--[[
|
||||
-- opts参数说明, 所有参数都是可选的
|
||||
-- 1. opts.url string 升级所需要的URL, 若使用合宙iot平台,则不需要填
|
||||
-- 2. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 3. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 4. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 5. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 6. opts.firmware_name string 固件名称,默认是 _G.PROJECT.. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
-- 7. opts.server_cert string 服务器证书, 默认不使用
|
||||
-- 8. opts.client_cert string 客户端证书, 默认不使用
|
||||
-- 9. opts.client_key string 客户端私钥, 默认不使用
|
||||
-- 10. opts.client_password string 客户端私钥口令, 默认不使用
|
||||
-- 11. opts.method string 请求方法, 默认是GET
|
||||
-- 12. opts.headers table 额外添加的请求头,默认不需要
|
||||
-- 13. opts.body string 额外添加的请求body,默认不需要
|
||||
]]
|
||||
local opts = {
|
||||
-- 使用的是自定义的URL,此处写的URL是不存在的一个无效URL,自己测试时,需要替换为自己的有效URL
|
||||
url = "###http://your_fota_packet_url.bin",
|
||||
-- 合宙IOT平台的默认升级URL, 不填就是这个默认值
|
||||
-- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
|
||||
-- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
|
||||
-- 如果不加###,则默认会上传如下参数
|
||||
-- 1. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 2. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 3. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 4. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 5. opts.firmware_name string 底层版本号
|
||||
|
||||
-- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
|
||||
-- version = ""
|
||||
-- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
|
||||
}
|
||||
|
||||
function fota_task_func()
|
||||
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("fota_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
|
||||
-- 检测到了IP_READY消息
|
||||
log.info("fota_task_func", "recv IP_READY", socket.dft())
|
||||
----这个判断是提醒要设置url的,且不要使用本文中的测试服务器,实际生产请删除
|
||||
if not opts.url or string.find(opts.url,"airtest.openluat.com") then
|
||||
while true do
|
||||
sys.wait(1000)
|
||||
log.info("fota", "当前URL",opts.url,"请修改正确的url")
|
||||
end
|
||||
end
|
||||
|
||||
log.info("开始检查升级")
|
||||
libfota2.request(fota_cb, opts)
|
||||
end
|
||||
|
||||
--创建并且启动一个task
|
||||
--运行这个task的主函数fota_task_func
|
||||
sys.taskInit(fota_task_func)
|
||||
-- 演示定时自动升级, 每隔4小时自动检查一次
|
||||
sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, opts)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user