Explorar o código

add:添加780epm的fota demo

mw hai 4 meses
pai
achega
21ec069f32
Modificáronse 40 ficheiros con 1702 adicións e 18 borrados
  1. 252 0
      module/Air780EPM/demo/aircloud/excloud_test.lua
  2. 65 0
      module/Air780EPM/demo/aircloud/main.lua
  3. 43 0
      module/Air780EPM/demo/aircloud/netdrv/netdrv_4g.lua
  4. 69 0
      module/Air780EPM/demo/aircloud/netdrv/netdrv_eth_spi.lua
  5. 88 0
      module/Air780EPM/demo/aircloud/netdrv/netdrv_multiple.lua
  6. 57 0
      module/Air780EPM/demo/aircloud/netdrv/netdrv_pc.lua
  7. 34 0
      module/Air780EPM/demo/aircloud/netdrv_device.lua
  8. 149 0
      module/Air780EPM/demo/aircloud/readme.md
  9. BIN=BIN
      module/Air780EPM/demo/aircloud/test.jpg
  10. 70 0
      module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_file.lua
  11. BIN=BIN
      module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_uart.bin
  12. 217 0
      module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_uart.lua
  13. 96 0
      module/Air780EPM/demo/fota/fota(使用fota核心库)/main.lua
  14. 42 0
      module/Air780EPM/demo/fota/fota(使用fota核心库)/main.py
  15. 375 0
      module/Air780EPM/demo/fota/fota(使用fota核心库)/readme.md
  16. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/README.md
  17. 2 2
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/air_srv_fota.lua
  18. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/main.lua
  19. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_4g.lua
  20. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_eth_spi.lua
  21. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_multiple.lua
  22. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv_device.lua
  23. 2 2
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/psm_power_fota.lua
  24. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_main.lua
  25. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_receiver.lua
  26. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_sender.lua
  27. 3 3
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/update.lua
  28. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/README.md
  29. 3 3
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/customer_srv_fota.lua
  30. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/main.lua
  31. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_4g.lua
  32. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_eth_spi.lua
  33. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_multiple.lua
  34. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv_device.lua
  35. 4 4
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/psm_power_fota.lua
  36. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_main.lua
  37. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_receiver.lua
  38. 0 0
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_sender.lua
  39. 4 4
      module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/update.lua
  40. 127 0
      module/Air780EPM/demo/fota/readme.md

+ 252 - 0
module/Air780EPM/demo/aircloud/excloud_test.lua

@@ -0,0 +1,252 @@
+--[[
+@module  excloud_test
+@summary excloud测试文件
+@version 1.0
+@date    2025.09.22
+@author  孟伟
+@usage
+本demo演示的功能为:
+本demo演示了excloud扩展库的完整使用流程,包括:
+1. 设备连接与认证
+2. 数据上报与接收
+3. 运维日志管理
+4. 文件上传功能
+5. 心跳保活机制
+]]
+-- 导入excloud库
+local excloud = require("excloud")
+
+-- 注册回调函数
+-- 注册回调函数
+function on_excloud_event(event, data)
+    log.info("用户回调函数", event, json.encode(data))
+
+    if event == "connect_result" then
+        if data.success then
+            log.info("连接成功")
+            sys.publish("aircloud_connected")
+        else
+            log.info("连接失败: " .. (data.error or "未知错误"))
+        end
+    elseif event == "auth_result" then
+        if data.success then
+            log.info("认证成功")
+        else
+            log.info("认证失败: " .. data.message)
+        end
+    elseif event == "message" then
+        log.info("收到消息, 流水号: " .. data.header.sequence_num)
+
+        -- 处理服务器下发的消息
+        for _, tlv in ipairs(data.tlvs) do
+            log.info("TLV字段", "含义:", tlv.field, "类型:", tlv.type, "值:", tlv.value)
+
+            if tlv.field == excloud.FIELD_MEANINGS.CONTROL_COMMAND then
+                log.info("收到控制命令: " .. tostring(tlv.value))
+
+                -- 处理控制命令并发送响应
+                local response_ok, err_msg = excloud.send({
+                    {
+                        field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
+                        data_type = excloud.DATA_TYPES.UNICODE,
+                        value = "命令执行成功"
+                    }
+                }, false)
+
+                if not response_ok then
+                    log.info("发送控制响应失败: " .. err_msg)
+                end
+            end
+        end
+    elseif event == "disconnect" then
+        log.warn("与服务器断开连接")
+    elseif event == "reconnect_failed" then
+        log.info("重连失败,已尝试 " .. data.count .. " 次")
+    elseif event == "send_result" then
+        if data.success then
+            log.info("发送成功,流水号: " .. data.sequence_num)
+        else
+            log.info("发送失败: " .. data.error_msg)
+        end
+
+    elseif event == "mtn_log_upload_start" then
+        log.info("运维日志上传开始", "文件数量:", data.file_count)
+
+    elseif event == "mtn_log_upload_progress" then
+        log.info("运维日志上传进度",
+                 "当前文件:", data.current_file,
+                 "总数:", data.total_files,
+                 "文件名:", data.file_name,
+                 "状态:", data.status)
+
+    elseif event == "mtn_log_upload_complete" then
+        log.info("运维日志上传完成",
+                 "成功:", data.success_count,
+                 "失败:", data.failed_count,
+                 "总计:", data.total_files)
+    end
+end
+
+-- 注册回调
+excloud.on(on_excloud_event)
+-- 主任务函数
+function excloud_task_func()
+    -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+    while not socket.adapter(socket.dft()) do
+        log.warn("excloud_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
+    -- -- 配置excloud参数
+    local ok, err_msg = excloud.setup({
+        use_getip = true, -- 使用getip服务
+        device_type = 1,   -- 4G设备
+        auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi",
+        transport = "tcp",       -- 使用TCP传输
+        auto_reconnect = true,   -- 自动重连
+        reconnect_interval = 10, -- 重连间隔(秒)
+        max_reconnect = 5,       -- 最大重连次数
+        mtn_log_enabled = true,  -- 启用运维日志
+        mtn_log_blocks = 2,      -- 日志文件块数
+        mtn_log_write_way = excloud.MTN_LOG_CACHE_WRITE  -- 缓存写入方式
+    })
+
+
+    --不使用getip服务,注意把use_getip设置为false
+    -- local ok, err_msg = excloud.setup({
+    --     use_getip = false,                             -- 不使用getip服务
+    --     device_type = 1,                               -- 设备类型: 4G
+    --     host = "112.125.89.8",                         -- 服务器地址
+    --     port = 32585,                                  -- 服务器端口
+    --     auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi", -- 鉴权密钥
+    --     transport = "tcp",                             -- 使用TCP传输
+    --     auto_reconnect = true,                         -- 自动重连
+    --     reconnect_interval = 10,                       -- 重连间隔(秒)
+    --     max_reconnect = 5,                             -- 最大重连次数
+    --     mtn_log_enabled = true                         -- 启用运维日志
+    -- })
+
+    -- -- 配置excloud参数,虚拟设备链接
+    -- local ok, err_msg = excloud.setup({
+    --     use_getip = true, --使用getip服务
+    --     device_type = 9,
+    --     auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi",
+    --     virtual_phone_number = "15893470522",  -- 11位手机号
+    --     virtual_serial_num = 1,                -- 序列号(0-999)
+    --     transport = "tcp", -- 由于mqtt链接需要使用imei,虚拟设备没有,所以只能使用TCP传输
+    --     mtn_log_enabled = true
+    -- })
+
+    if not ok then
+        log.info("初始化失败: " .. err_msg)
+        return
+    end
+    log.info("excloud初始化成功")
+
+    -- 开启excloud服务
+    local ok, err_msg = excloud.open()
+    if not ok then
+        log.info("开启excloud服务失败: " .. err_msg)
+        return
+    end
+    log.info("excloud服务已开启")
+    -- 启动自动心跳,默认5分钟一次的心跳
+    excloud.start_heartbeat()
+    log.info("自动心跳已启动")
+
+    -- 启动3分钟一次的心跳,可配置自定义内容
+    -- excloud.start_heartbeat(180, {
+    --     { field_meaning = excloud.FIELD_MEANINGS.TIMESTAMP,
+    --     data_type = excloud.DATA_TYPES.INTEGER,
+    --     value = os.time() }
+    -- })
+
+    -- 停止自动心跳
+    --excloud.stop_heartbeat()
+    -- 记录启动日志
+    --excloud.mtn_log("system", "设备启动完成", "version", "1.0.0")
+
+    -- 主循环:定期上报数据
+
+    while true do
+        -- 每30秒上报一次数据
+        sys.wait(30000)
+        -- 检查连接状态
+        local status = excloud.status()
+        if not status.is_connected then
+            log.warn("设备未连接,跳过数据上报")
+
+        else
+            -- 上报基础状态数据
+            local ok, err_msg = excloud.send({
+                {
+                    field_meaning = excloud.FIELD_MEANINGS.SIGNAL_STRENGTH_4G,
+                    data_type = excloud.DATA_TYPES.INTEGER,
+                    value = 22  -- 信号强度
+                },
+                {
+                    field_meaning = excloud.FIELD_MEANINGS.SIM_ICCID,
+                    data_type = excloud.DATA_TYPES.ASCII,
+                    value = "89860118801012345678"  -- SIM卡ICCID
+                },
+                {
+                    field_meaning = excloud.FIELD_MEANINGS.TIMESTAMP,
+                    data_type = excloud.DATA_TYPES.INTEGER,
+                    value = os.time()
+                }
+            }, false)
+
+            if ok then
+                log.info("基础数据上报成功")
+            else
+                log.error("基础数据上报失败:", err_msg)
+            end
+        end
+
+    end
+end
+
+-- 启动主任务
+sys.taskInit(excloud_task_func)
+--上传图片示例
+function upload_image_fun()
+    -- 等待连接建立
+    sys.waitUntil("aircloud_connected", 10000)
+    -- 上传图片
+    log.info("开始上传图片")
+    if not excloud.status().is_connected then
+        log.info("设备未连接,跳过图片上传")
+        return
+    end
+    if io.exists("/luadb/test.jpg") then
+        local ok, err = excloud.upload_image("/luadb/test.jpg", "test.jpg")
+        if ok then
+            log.info("图片上传成功")
+        else
+            log.error("图片上传失败:", err)
+        end
+    else
+        log.warn("测试图片文件不存在")
+    end
+end
+
+sys.taskInit(upload_image_fun)
+
+-- 运维日志测试示例
+function mtnlog_test_task()
+    local test_count = 0
+    while true do
+        test_count = test_count + 1
+
+        excloud.mtn_log("mtn_test", test_count)
+        -- 每30秒记录一次
+        sys.wait(1000)
+    end
+end
+
+sys.taskInit(mtnlog_test_task)

+ 65 - 0
module/Air780EPM/demo/aircloud/main.lua

@@ -0,0 +1,65 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.22
+@author  孟伟
+@usage
+本demo演示的功能为:
+演示excloud扩展库的使用。
+]]
+
+--[[
+必须定义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进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "excloud_test"
+VERSION = "001.000.000"
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+end
+
+-- 如果内核固件支持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)
+
+-- -- 加载网络驱动设备功能模块
+require "netdrv_device"
+
+-- 加载excloud测试模块
+require"excloud_test"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 43 - 0
module/Air780EPM/demo/aircloud/netdrv/netdrv_4g.lua

@@ -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)
+
+-- 在Air8000上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
+

+ 69 - 0
module/Air780EPM/demo/aircloud/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,69 @@
+--[[
+@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)
+
+
+-- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
+-- 本demo使用Air780epm开发板测试,开发板上的硬件配置为:
+-- 使用spi0,片选引脚使用GPIO8
+-- 如果使用的硬件不是Air8000开发板,根据自己的硬件配置修改以下参数
+exnetif.set_priority_order({
+    {
+        ETHERNET = {
+            pwrpin = 140, 
+            tp = netdrv.CH390,
+            opts = {spi = 0, cs = 8}
+        }
+    }
+})

+ 88 - 0
module/Air780EPM/demo/aircloud/netdrv/netdrv_multiple.lua

@@ -0,0 +1,88 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过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}
+                }
+            },
+
+            -- 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)

+ 57 - 0
module/Air780EPM/demo/aircloud/netdrv/netdrv_pc.lua

@@ -0,0 +1,57 @@
+--[[
+@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)
+
+
+
+-- 下面这段代码是在PC模拟器上构造一个唯一的ID;不同PC上运行模拟器,这个ID要不一样
+-- 因为mqtt client使用的是这个ID做为client id,如果不同PC上的id一样,模拟器在不同PC上同时运行时,mqtt client就会频繁出现被踢下线的问题
+-- 目前模拟器上还没有合适的接口获取唯一ID,所以此处先简单的构造一个ID,需要手动保证ID唯一,在此处我简单使用了zhutianhua1做为ID
+_G.mobile = {}
+function mobile.imei()
+    return "zhutianhua1"
+    -- log.info("mcu.unique_id()", mcu.unique_id())
+    -- return mcu.unique_id().."zhutianhua1"
+end

+ 34 - 0
module/Air780EPM/demo/aircloud/netdrv_device.lua

@@ -0,0 +1,34 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
+1、netdrv_4g:socket.LWIP_GP,4G网卡;
+2、netdrv_ethernet_spi:socket.LWIP_USER1,通过SPI外挂CH390H芯片的以太网卡;
+3、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
+4、netdrv_pc:pc模拟器上的网卡
+
+根据自己的项目需求,只需要require以上四种中的一种即可;
+
+
+本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
+]]
+
+
+-- 根据自己的项目需求,只需要require以下四种中的一种即可;
+
+-- 加载“4G网卡”驱动模块
+require "netdrv_4g"
+
+
+-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
+-- require "netdrv_eth_spi"
+
+-- 加载“可以配置优先级的多种网卡”驱动模块
+-- require "netdrv_multiple"
+
+-- 加载“pc模拟器网卡”驱动模块
+-- require "netdrv_pc"

+ 149 - 0
module/Air780EPM/demo/aircloud/readme.md

@@ -0,0 +1,149 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单spi以太网卡,单pc模拟器网卡,多网卡)中的任何一种网卡;
+
+3、excloud.lua: aircloud的实现库
+
+4、excloud_test.lua:aircloud的应用模块,实现了aircloud的应用场景。
+
+## 演示功能概述
+
+使用Air780EPM V1.3开发板测试aircloud功能
+
+AirCloud 概述:AirCloud 是 LuatOS 物联网设备云服务通信协议,提供设备连接、数据上报、远程控制和文件上传等核心功能。excloud 扩展库是 AirCloud 协议的实现,通过该库设备可以快速接入云服务平台,实现远程监控和管理。
+
+本demo演示了excloud扩展库的完整使用流程,包括:
+1. 设备连接与认证
+2. 数据上报与接收
+3. 运维日志管理
+4. 文件上传功能
+5. 心跳保活机制
+
+## 演示硬件环境
+
+![img](https://docs.openluat.com/air780epm/luatos/app/driver/eth/image/RFSvb75NRoEWqYxfCRVcVrOKnsf.jpg)
+
+1、Air780EPM V1.3版本开发板一块+可上网的sim卡一张+4g天线一根+网线一根:
+
+- sim卡插入开发板的sim卡槽
+- 天线装到开发板上
+- 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
+
+2、TYPE-C USB数据线一根 ,Air780EPM V1.3版本开发板和数据线的硬件接线方式为:
+
+- Air780EPM V1.3版本开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EPM V2016版本固件](https://gitee.com/link?target=https%3A%2F%2Fdocs.openluat.com%2Fair780epm%2Fluatos%2Ffirmware%2Fversion%2F)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+- 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+- 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+- 如果需要多网卡,打开require "netdrv_multiple",其余注释掉
+
+3、修改excloud_test.lua文件中excloud.setup接口的相关参数,根据自己需求配置连接协议、是否启用运维日志、项目key、设备类型,是否启用getip等内容。
+
+4、烧录好后,板子开机同时在luatools上查看日志:
+
+```lua
+[2025-10-16 17:59:41.066][000000003.897] I/user.[excloud]excloud.setup 初始化成功 设备ID: 862419074072389
+[2025-10-16 17:59:41.072][000000003.897] I/user.excloud初始化成功
+[2025-10-16 17:59:41.074][000000003.897] I/user.[excloud]首次连接,获取服务器信息...
+[2025-10-16 17:59:41.077][000000003.898] I/user.[excloud]excloud.getip 类型: 3 key: VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi-862419074072389
+[2025-10-16 17:59:41.080][000000003.904] D/socket connect to gps.openluat.com,443
+[2025-10-16 17:59:41.083][000000003.904] dns_run 676:gps.openluat.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-10-16 17:59:41.144][000000003.969] dns_run 693:dns all done ,now stop
+[2025-10-16 17:59:41.706][000000004.539] I/user.httpplus 等待服务器完成响应
+[2025-10-16 17:59:41.862][000000004.693] I/user.httpplus 等待服务器完成响应
+[2025-10-16 17:59:41.893][000000004.712] I/user.httpplus 服务器已完成响应,开始解析响应
+[2025-10-16 17:59:41.924][000000004.745] I/user.[excloud]excloud.getip响应 HTTP Code: 200 Body: {"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108},"imginfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/image","data_key":"f","data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""}},"audinfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/audio","data_key":"f","data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""}}} Body: nil Cannot serialise userdata: type not supported
+[2025-10-16 17:59:41.933][000000004.746] I/user.[excloud]excloud.getip响应 JSON: ok
+[2025-10-16 17:59:41.938][000000004.747] I/user.[excloud]excloud.getip 124.71.128.165 9108
+[2025-10-16 17:59:41.943][000000004.748] I/user.[excloud]excloud.getip 成功: true 结果: {"audinfo":{"data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""},"data_key":"f","url":"https:\/\/gps.openluat.com\/iot\/aircloud\/upload\/audio"},"imginfo":{"data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""},"data_key":"f","url":"https:\/\/gps.openluat.com\/iot\/aircloud\/upload\/image"},"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108}}
+[2025-10-16 17:59:41.947][000000004.748] I/user.[excloud]获取服务器信息结果 true {"audinfo":{"data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""},"data_key":"f","url":"https:\/\/gps.openluat.com\/iot\/aircloud\/upload\/audio"},"imginfo":{"data_param":{"key":"WMi7G6Kcx8H7UoknB2Knt8btqbDvTAEvAWeQgg","tip":""},"data_key":"f","url":"https:\/\/gps.openluat.com\/iot\/aircloud\/upload\/image"},"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108}} 图片url https://gps.openluat.com/iot/aircloud/upload/image
+[2025-10-16 17:59:41.952][000000004.749] I/user.[excloud]创建TCP连接
+[2025-10-16 17:59:41.959][000000004.750] D/socket connect to 124.71.128.165,9108
+[2025-10-16 17:59:41.962][000000004.750] network_socket_connect 1605:network 0 local port auto select 50642
+[2025-10-16 17:59:41.965][000000004.751] I/user.[excloud]TCP连接结果 true false
+[2025-10-16 17:59:41.969][000000004.752] I/user.[excloud]excloud service started
+[2025-10-16 17:59:41.976][000000004.752] I/user.excloud服务已开启
+[2025-10-16 17:59:41.979][000000004.753] I/user.[excloud]excloud 自动心跳已启动,间隔 300 秒
+[2025-10-16 17:59:41.983][000000004.792] network_default_socket_callback 1120:before process socket 1,event:0xf2000009(连接成功),state:3(正在连接),wait:2(等待连接完成)
+[2025-10-16 17:59:41.985][000000004.792] network_default_socket_callback 1124:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-10-16 17:59:41.997][000000004.793] I/user.[excloud]socket cb userdata: 0C199080 33554449 0
+[2025-10-16 17:59:42.002][000000004.794] I/user.[excloud]socket TCP连接成功
+[2025-10-16 17:59:42.006][000000004.794] I/user.用户回调函数 connect_result {"success":true}
+[2025-10-16 17:59:42.008][000000004.794] I/user.连接成功
+[2025-10-16 17:59:42.011][000000004.797] I/user.[excloud]发送数据333 16 3 VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi-862419074072389-20250228145308A686442A0057563473
+[2025-10-16 17:59:42.014][000000004.798] I/user.[excloud]tlv发送数据长度4 85
+[2025-10-16 17:59:42.020][000000004.799] I/user.[excloud]构建消息头 $ @r8
+[2025-10-16 17:59:42.023][000000004.801] I/user.[excloud]发送消息长度 16 85 101 0186241907407238000100550000001130100051566D68744F62383145675A617536597975755A4A7A7746366F554E47436258692D3836323431393037343037323338392D3230323530323238313435333038413638363434324130303537353633343733 202
+[2025-10-16 17:59:42.027][000000004.802] I/user.用户回调函数 send_result {"sequence_num":0,"success":true,"error_msg":"Send successful"}
+[2025-10-16 17:59:42.031][000000004.802] I/user.发送成功,流水号: 0
+[2025-10-16 17:59:42.035][000000004.803] I/user.[excloud]数据发送成功 101 字节
+[2025-10-16 17:59:42.041][000000004.848] network_default_socket_callback 1120:before process socket 1,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-10-16 17:59:42.045][000000004.848] network_default_socket_callback 1124:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-10-16 17:59:42.048][000000004.849] I/user.[excloud]socket cb userdata: 0C199080 33554450 0
+[2025-10-16 17:59:42.057][000000004.849] I/user.[excloud]socket 发送完成
+[2025-10-16 17:59:47.455][000000010.283] I/user.开始上传图片
+[2025-10-16 17:59:47.461][000000010.284] I/user.[excloud]开始文件上传 类型: 1 文件: test.jpg 大小: 199658
+[2025-10-16 17:59:47.465][000000010.286] I/user.[excloud]发送数据333 23 4 
+[2025-10-16 17:59:47.471][000000010.287] I/user.[excloud]tlv发送数据长度4 32
+[2025-10-16 17:59:47.483][000000010.289] I/user.[excloud]构建消息头 $ @r8
+[2025-10-16 17:59:47.486][000000010.290] I/user.[excloud]发送消息长度 16 32 48 018624190740723800020020000000014017001C031000040000000133110008746573742E6A70670312000400030BEA 96
+[2025-10-16 17:59:47.509][000000010.291] I/user.用户回调函数 send_result {"sequence_num":1,"success":true,"error_msg":"Send successful"}
+[2025-10-16 17:59:47.514][000000010.291] I/user.发送成功,流水号: 1
+[2025-10-16 17:59:47.518][000000010.292] I/user.[excloud]数据发送成功 48 字节
+[2025-10-16 17:59:47.523][000000010.295] D/socket connect to gps.openluat.com,443
+[2025-10-16 17:59:47.529][000000010.296] dns_run 676:gps.openluat.com state 0 id 2 ipv6 0 use dns server2, try 0
+[2025-10-16 17:59:47.545][000000010.326] dns_run 693:dns all done ,now stop
+[2025-10-16 17:59:47.549][000000010.352] network_default_socket_callback 1120:before process socket 1,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-10-16 17:59:47.554][000000010.352] network_default_socket_callback 1124:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-10-16 17:59:47.557][000000010.353] I/user.[excloud]socket cb userdata: 0C199080 33554450 0
+[2025-10-16 17:59:47.561][000000010.353] I/user.[excloud]socket 发送完成
+[2025-10-16 17:59:49.789][000000012.616] I/user.httpplus 等待服务器完成响应
+[2025-10-16 17:59:49.913][000000012.744] I/user.httpplus 等待服务器完成响应
+[2025-10-16 17:59:49.961][000000012.792] I/user.httpplus 服务器已完成响应,开始解析响应
+[2025-10-16 17:59:50.006][000000012.825] I/user.[excloud]excloud.getip文件上传响应 HTTP Code: 200 Body: {"info":"iot./iot/aircloud/upload/image->iam-server./iam/tenant/getbyoid/6268048492107342913","code":0,"trace":"code:iot./iot/aircloud/upload/image->iam-server./iam/tenant/getbyoid/6268048492107342913,  trcace:clear 1 temp suc infos.","log":"^^^","value":{"uri":"/vsna/luatos/336677/aircloud_image/5411605038321602040/2025-10/test.jpg","size":"194.00KB","thumb":"/vsna/luatos/336677/aircloud_image/5411605038321602040/2025-10/testt.jpg"}}
+[2025-10-16 17:59:50.024][000000012.825] Body:
+[2025-10-16 17:59:50.035][000000012.825]  nil Cannot serialise userdata: type not supported
+[2025-10-16 17:59:50.050][000000012.826] E/user.文件上传失败 服务器返回错误: nil 响应: nil
+[2025-10-16 17:59:50.064][000000012.829] I/user.[excloud]发送数据333 24 4 
+[2025-10-16 17:59:50.077][000000012.830] I/user.[excloud]tlv发送数据长度4 32
+[2025-10-16 17:59:50.090][000000012.831] I/user.[excloud]构建消息头 $ @r8
+[2025-10-16 17:59:50.103][000000012.832] I/user.[excloud]发送消息长度 16 32 48 018624190740723800030020000000014018001C031000040000000133110008746573742E6A70670313000400000000 96
+[2025-10-16 17:59:50.114][000000012.834] I/user.用户回调函数 send_result {"sequence_num":2,"success":true,"error_msg":"Send successful"}
+[2025-10-16 17:59:50.127][000000012.835] I/user.发送成功,流水号: 2
+[2025-10-16 17:59:50.140][000000012.835] I/user.[excloud]数据发送成功 48 字节
+[2025-10-16 17:59:50.153][000000012.835] E/user.图片上传失败: 服务器返回错误: nil
+[2025-10-16 17:59:50.169][000000012.876] network_default_socket_callback 1120:before process socket 1,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-10-16 17:59:50.182][000000012.876] network_default_socket_callback 1124:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-10-16 17:59:50.191][000000012.877] I/user.[excloud]socket cb userdata: 0C199080 33554450 0
+[2025-10-16 17:59:50.205][000000012.877] I/user.[excloud]socket 发送完成
+[2025-10-16 18:00:11.918][000000034.753] I/user.[excloud]发送数据333 782 0 22
+[2025-10-16 18:00:11.941][000000034.755] I/user.[excloud]发送数据333 783 3 8 
+[2025-10-16 18:00:11.955][000000034.755] I/user.[excloud]tlv发送数据长度4 13
+[2025-10-16 18:00:11.969][000000034.757] I/user.[excloud]构建消息头 $ @r8
+[2025-10-16 18:00:11.984][000000034.758] I/user.[excloud]发送消息长度 16 13 29 01862419074072380004000D00000001030E000400000016330F000138 58
+[2025-10-16 18:00:11.994][000000034.762] I/user.用户回调函数 send_result {"sequence_num":3,"success":true,"error_msg":"Send successful"}
+[2025-10-16 18:00:12.010][000000034.762] I/user.发送成功,流水号: 3
+[2025-10-16 18:00:12.022][000000034.762] I/user.[excloud]数据发送成功 29 字节
+[2025-10-16 18:00:12.035][000000034.763] I/user.数据发送成功
+[2025-10-16 18:00:12.049][000000034.873] network_default_socket_callback 1120:before process socket 1,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-10-16 18:00:12.061][000000034.874] network_default_socket_callback 1124:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-10-16 18:00:12.075][000000034.874] I/user.[excloud]socket cb userdata: 0C199080 33554450 0
+[2025-10-16 18:00:12.086][000000034.875] I/user.[excloud]socket 发送完成
+```
+
+

BIN=BIN
module/Air780EPM/demo/aircloud/test.jpg


+ 70 - 0
module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_file.lua

@@ -0,0 +1,70 @@
+--[[
+@module  fota_file
+@summary 文件系统FOTA升级功能模块
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+-- 文件系统FOTA升级功能
+-- 提供从文件系统直接读取升级包进行固件升级的功能
+-- 可以使用luatools工具的烧录系统文件功能将升级包直接烧录到文件系统中,
+
+本文件没有对外接口,直接在main.lua中require "fota_file"就可以加载运行;
+]]
+
+local function fileUpgradeTask()
+    -- 等待系统稳定后再开始升级
+    sys.wait(10000)
+
+    log.info("FOTA_FILE", "=== 开始文件系统升级 ===")
+
+    -- 步骤1: 初始化FOTA流程
+    log.info("FOTA_FILE", "初始化FOTA...")
+    if not fota.init() then
+        log.error("FOTA_FILE", "FOTA初始化失败")
+        return
+    end
+
+    -- 步骤2: 等待底层准备就绪
+    log.info("FOTA_FILE", "等待底层准备...")
+    while not fota.wait() do
+        sys.wait(100)
+    end
+    log.info("FOTA_FILE", "底层准备就绪")
+
+    -- 步骤3: 从文件系统读取升级包并启动升级
+    local filePath = "/update.bin"
+    log.info("FOTA_FILE", "开始读取升级文件:", filePath)
+    local result, isDone, cache = fota.file(filePath)
+    log.info("FOTA_FILE", "升级文件写入flash中的fota分区结果", result, isDone, cache)
+
+    -- 步骤4: 结束写入fota分区
+    log.info("FOTA_FILE", "结束写入fota分区...")
+    local result, isDone = fota.isDone()
+    log.info("FOTA_FILE", "写入fota分区状态", "结果:", result, "完成:", isDone)
+    if result then
+        -- 步骤5: 处理写入结果
+        if isDone then
+            -- 升级文件成功写入flash中的fota分区,准备重启设备;
+            -- 设备重启后,在初始化阶段的运行过程中会自动应用fota分区中的数据完成升级,最终升级结果可以通过观察日志中的版本号来区分。
+            log.info("FOTA_FILE", "升级成功,准备重启设备")
+            -- 调用fota.finish(true)结束升级流程,参数true表示正确走完流程。
+            fota.finish(true)
+            -- 可选:删除升级包文件
+            -- os.remove("/update.bin")
+            sys.wait(2000)
+            rtos.reboot()
+        else
+            log.error("FOTA_FILE", "升级失败")
+            -- -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
+            fota.finish(false)
+        end
+    else
+        log.error("FOTA_FILE", "升级失败:检查写入状态失败")
+        -- -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
+        fota.finish(false)
+    end
+end
+
+-- 启动文件升级任务
+sys.taskInit(fileUpgradeTask)

BIN=BIN
module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_uart.bin


+ 217 - 0
module/Air780EPM/demo/fota/fota(使用fota核心库)/fota_uart.lua

@@ -0,0 +1,217 @@
+--[[
+@module  fota_uart
+@summary 串口FOTA升级功能模块
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+-- 串口FOTA升级功能
+-- 提供通过串口分段接收升级包数据进行固件升级的功能
+
+用法:
+1. 先把脚本和固件烧录到模块里, 并确认开机
+2. 按下Power键启动串口升级模式(设备会等待升级数据)
+3. 在电脑端操作:进入命令行程序,执行 `python main.py` 进行升级,需要保证升级文件名字为 `fota_uart.bin`,并且和 `main.py` 在同一目录下
+    注意:运行`python main.py`需要确保电脑安装了Python环境。
+4. 观察luatools的输出和main.py的输出
+5. 模块接收正确的升级数据后,会提示1秒后重启
+6. 本demo自带的脚本升级包,仅加了一条打印和修改版本号
+
+串口通讯过程说明
+串口升级采用简单的文本协议进行握手和数据传输控制:
+协议流程:
+1. 上位机发送:#FOTA\n
+2. 设备回复:#FOTA RDY\n
+3. 上位机发送:256字节数据包
+4. 设备回复:#FOTA NEXT\n(请求下一包)
+5. 重复步骤3-4直到所有数据发送完成
+6. 设备回复:#FOTA OK\n(升级成功)
+7. 设备自动重启
+
+注意:
+- 本demo默认是走虚拟串口进行交互, 如需改成物理串口, 修改uart_id和main.py
+- 升级过程中如果发生错误,串口会自动关闭,需要重新按Power键开启
+- 升级成功设备会自动重启
+
+本文件没有对外接口,直接在main.lua中require "fota_uart"就可以加载运行;
+]]
+
+-- 定义所需要的UART编号
+-- uart_id = 1    -- UART1, 通常也是MAIN_UART
+local uart_id = uart.VUART_0 -- 虚拟USB串口
+
+-- 全局变量
+local uart_zbuff = nil
+local uart_fota_state = 0
+local uart_rx_counter = 0
+local uart_fota_writed = 0
+local upgrade_active = false  -- 升级是否激活标志
+
+-- 按键回调函数 - Power键
+local function power_key_callback()
+    if not upgrade_active then
+        log.info("FOTA_UART", "Power键按下,启动串口升级模式")
+        -- 初始化串口和缓冲区
+        uart_zbuff = zbuff.create(1024)
+        uart.setup(uart_id, 115200)
+        uart.on(uart_id, "receive", uart_cbfun)
+        upgrade_active = true
+        uart_fota_state = 0
+        uart_rx_counter = 0
+        uart_fota_writed = 0
+        -- 发布事件,唤醒升级任务
+        sys.publish("UART_UPGRADE_START")
+    else
+        log.info("FOTA_UART", "升级模式已激活,请等待当前升级完成")
+    end
+end
+
+-- 配置Power键
+gpio.setup(gpio.PWR_KEY, power_key_callback, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 200ms去抖
+
+-- 清理资源的函数
+local function cleanup_resources()
+    if uart_zbuff then
+        uart_zbuff:del()
+        uart_zbuff = nil
+    end
+    uart.close(uart_id)  -- 关闭串口
+    upgrade_active = false
+    uart_fota_state = 0
+    log.info("FOTA_UART", "资源已清理,串口已关闭")
+end
+
+-- 串口接收回调函数
+function uart_cbfun(id, len)
+    if not upgrade_active or not uart_zbuff then
+        return
+    end
+
+    -- 防御缓冲区超标的情况
+    if uart_zbuff:used() > 8192 then
+        log.warn("fota", "uart_zbuff待处理的数据太多了,强制清空")
+        uart_zbuff:del()
+    end
+
+    while 1 do
+        local len = uart.rx(id, uart_zbuff)
+        if len <= 0 then
+            break
+        end
+        uart_rx_counter = uart_rx_counter + len
+        log.info("uart", "收到数据", len, "累计", uart_rx_counter)
+        -- 首次收到数据即发布事件,唤醒升级任务
+        if uart_fota_state == 0 then
+            sys.publish("UART_FOTA")
+        end
+    end
+end
+
+-- 串口升级任务
+local function uartUpgradeTask()
+    local fota_state = 0 -- 0还没开始, 1进行中
+
+    while 1 do
+        -- 等待升级启动信号
+        sys.waitUntil("UART_UPGRADE_START")
+        log.info("FOTA_UART", "升级任务已启动,等待数据...")
+
+        while upgrade_active do
+            -- 等待升级数据到来
+            sys.waitUntil("UART_FOTA", 1000)
+            if not upgrade_active then break end
+
+            local used = uart_zbuff and uart_zbuff:used() or 0
+            if used > 0 then
+                if fota_state == 0 then
+                    -- 等待FOTA的状态
+                    if used > 5 then
+                        local data = uart_zbuff:query()
+                        uart_zbuff:del()
+                        -- 如果接受到 #FOTA\n 代表数据要来了
+                        if data:startsWith("#FOTA") and data:endsWith("\n") then
+                            fota_state = 1
+                            log.info("fota", "检测到fota起始标记,进入FOTA状态", data)
+                            if fota.init() then
+                                -- 固件数据发送端应该在收到#FOTA RDY\n之后才开始发送数据
+                                uart.write(uart_id, "#FOTA RDY\n")
+                            else
+                                log.error("FOTA_UART", "FOTA初始化失败")
+                                cleanup_resources()
+                                break
+                            end
+                        end
+                    end
+                else
+                    -- 已进入升级状态:把收到的数据喂给fota.run
+                    uart_fota_writed = uart_fota_writed + used
+                    log.info("准备写入fota包", used, "累计写入", uart_fota_writed)
+                    local result, isDone, cache = fota.run(uart_zbuff)
+                    log.debug("fota.run", result, isDone, cache)
+                    uart_zbuff:del() -- 清空缓冲区
+
+                    if not result then
+                        -- 写入失败,退出升级状态并通知上位机
+                        log.error("fota", "出错了", result, isDone, cache)
+                        uart.write(uart_id, "#FOTA ERR\n")
+                        -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
+                        fota.finish(false)
+                        cleanup_resources()
+                        fota_state = 0
+                        break
+                    elseif isDone then
+                        -- 全部数据写入完成,等待底层校验结束
+                        local success = false
+                        for i = 1, 30 do  -- 最多等待3秒
+                            sys.wait(100)
+                            local succ, fotaDone = fota.isDone()
+                            if not succ then
+                                log.error("fota", "校验过程出错")
+                                uart.write(uart_id, "#FOTA ERR\n")
+                                fota.finish(false)
+                                cleanup_resources()
+                                fota_state = 0
+                                break
+                            end
+                            if fotaDone then
+                                uart_fota_state = 1
+                                -- 升级文件成功写入flash中的fota分区,准备重启设备;
+                                log.info("fota", "已完成,1s后重启")
+                                -- 调用fota.finish(true)结束升级流程,参数true表示正确走完流程。
+                                fota.finish(true)
+                                -- 反馈给上位机
+                                uart.write(uart_id, "#FOTA OK\n")
+                                sys.wait(1000)
+                                success = true
+                                rtos.reboot()
+                                break
+                            end
+                        end
+                        if not success then
+                            log.error("fota", "校验超时")
+                            uart.write(uart_id, "#FOTA ERR\n")
+                            fota.finish(false)
+                            cleanup_resources()
+                            fota_state = 0
+                        end
+                        break
+                    else
+                        -- 单包写入成功,通知上位机继续下发
+                        log.info("fota", "单包写入完成", used, "等待下一个包")
+                        uart.write(uart_id, "#FOTA NEXT\n")
+                    end
+                end
+            end
+        end
+
+        -- 重置状态,等待下次升级
+        fota_state = 0
+        uart_fota_state = 0
+        uart_rx_counter = 0
+        uart_fota_writed = 0
+    end
+end
+
+-- 启动串口升级任务
+sys.taskInit(uartUpgradeTask)

+ 96 - 0
module/Air780EPM/demo/fota/fota(使用fota核心库)/main.lua

@@ -0,0 +1,96 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+Air780EPM模块的两种FOTA升级方式:文件系统直接升级和串口分段升级;
+
+分两种不同的应用场景来演示固件升级的实现方法:
+
+1、文件系统直接升级:通过模组文件系统中的文件直接升级,代码演示通过luatools的烧录文件系统功能将升级包文件直接烧录到文件系统然后升级;
+
+2、分段升级:通过串口将升级包文件分多个片段发送,每个片段接收并写入,代码演示使用usb虚拟串口分段写入升级包升级;
+
+适用场景:
+    - 非标准数据传输 -> 串口、TCP、MQTT等自定义通道升级
+
+    - 流程精细控制 -> 需要自定义升级前后处理逻辑
+
+更多说明参考本目录下的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进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "fota_test"
+VERSION = "1.0.0" --不同于使用libfota2扩展库来升级必须是xxx.xxx.xxx的格式,这里可以自定义版本号格式。
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持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)
+
+
+
+-- 循环打印版本号, 方便看版本号变化, 非必须
+function print_version()
+    while 1 do
+        sys.wait(1000)
+        log.info("fota", "version", VERSION)
+        -- log.info("fota1111122222222222")
+    end
+end
+sys.taskInit(print_version)
+
+-- 方式1: 文件系统直接升级
+-- require("fota_file")
+
+-- 方式2: 分段写入升级,以串口来分段写入升级包
+require("fota_uart")
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 42 - 0
module/Air780EPM/demo/fota/fota(使用fota核心库)/main.py

@@ -0,0 +1,42 @@
+#!/usr/bin/python3
+# -*- coding: UTF-8 -*-
+
+import serial, serial.tools.list_ports, sys, argparse
+
+def find_virtual_port():
+    for port in serial.tools.list_ports.comports():
+        if port.vid == 0x19d1 and port.pid == 0x0001 and port.location and "x.6" in port.location:
+            return port.device
+    return None
+
+# 解析命令行参数
+parser = argparse.ArgumentParser()
+parser.add_argument('-p', '--port', help='串口名称,如COM1或/dev/ttyS0')
+args = parser.parse_args()
+
+# 确定使用的串口
+port = args.port or find_virtual_port()
+if not port:
+    print("错误: 未找到虚拟串口且未指定串口")
+    sys.exit(1)
+
+print(f"使用串口: {port}")
+
+try:
+    with serial.Serial(port, 115200, timeout=1) as ser:
+        ser.write(b"#FOTA\n")
+        data = ser.read(128)
+        if data and data.startswith(b"#FOTA"):
+            print("设备响应", data)
+            with open("fota_uart.bin", "rb") as f:
+                while fdata := f.read(256):
+                    print("发送升级包数据", len(fdata))
+                    ser.write(fdata)
+                    if resp := ser.read(128):
+                        print("设备响应", resp)
+            print("发送完毕,退出")
+        else:
+            print("设备没响应", data)
+except Exception as e:
+    print(f"错误: {e}")
+    sys.exit(1)

+ 375 - 0
module/Air780EPM/demo/fota/fota(使用fota核心库)/readme.md

@@ -0,0 +1,375 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、fota_file.lua:介绍了使用文件系统进行FOTA升级功能的实现模块,包括完整的升级流程;
+
+3、fota_uart.lua:介绍了使用串口分段进行FOTA升级功能的实现模块,包括完整的升级流程;
+
+4、main.py:Python脚本工具,用于通过串口分段发送升级包,演示分段升级的流程;
+
+5、fota_uart.bin:演示串口分段升级的升级包文件,升级内容仅升级版本号以及添加几行打印;
+
+## 演示功能概述
+
+FOTA是固件远程升级的简称,用于设备固件的远程更新和维护;
+
+本demo演示的核心功能为:
+
+Air780EPM模块的两种FOTA升级方式:文件系统直接升级和串口分段升级;
+
+分两种不同的应用场景来演示固件升级的实现方法:
+
+1、文件系统直接升级:通过模组文件系统中的文件直接升级,代码演示通过luatools的烧录文件系统功能将升级包文件直接烧录到文件系统然后升级;
+
+2、分段升级:通过串口将升级包文件分多个片段发送,每个片段接收并写入,代码演示使用usb虚拟串口分段写入升级包升级;
+
+适用场景:
+
+    非标准数据传输 -> 串口、TCP、MQTT等自定义通道升级
+
+    流程精细控制 -> 需要自定义升级前后处理逻辑
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780epm/luatos/app/driver/eth/image/RFSvb75NRoEWqYxfCRVcVrOKnsf.jpg)
+
+1、Air780EPM V1.3版本开发板一块
+
+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 V2016版本固件)](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、Python 3 环境(用于运行main.py发送升级包)
+
+## 演示操作步骤
+
+### 方式1:文件系统直接升级
+
+1、搭建好演示硬件环境
+
+2、修改demo脚本代码,取消`main.lua`中`require("fota_file")`的注释,注释掉`require("fota_uart")`
+
+3、使用Luatools制作升级包,先把新旧版本分别生成量产文件,然后再制作升级包,工具上栏 luatOS->固件工具->差分包/整包升级包制作,将制作好的升级包放到luatools工具上面的烧录文件系统功能。
+
+4、Luatools烧录内核固件和修改前的demo脚本代码,以及升级包文件。烧录成功后,自动开机运行。
+
+5、可以看到如下日志:
+日志分析如下:
+
+1. 开始升级,读取文件系统目录下的升级包文件/update.bin
+
+2. FOTA初始化 → 底层就绪 → 文件写入 → MD5校验通过
+
+3. 升级完成,版本验证成功
+
+4. 设备自动重启
+
+5. 新版本 1.0.1 正常运行,新增日志确认升级成功
+
+结果:文件系统FOTA升级完全成功,版本从1.0.0升级到1.0.1
+
+```lua
+[2025-10-24 17:56:47.892][000000005.840] D/mobile bearer act 0, result 0
+[2025-10-24 17:56:47.902][000000005.841] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-10-24 17:56:47.911][000000005.890] D/mobile TIME_SYNC 0
+[2025-10-24 17:56:48.203][000000006.204] I/user.fota version 1.0.0
+[2025-10-24 17:56:49.208][000000007.204] I/user.fota version 1.0.0
+[2025-10-24 17:56:50.205][000000008.204] I/user.fota version 1.0.0
+[2025-10-24 17:56:51.205][000000009.205] I/user.fota version 1.0.0
+[2025-10-24 17:56:52.199][000000010.205] I/user.fota version 1.0.0
+[2025-10-24 17:56:52.207][000000010.210] I/user.FOTA_FILE === 开始文件系统升级 ===
+[2025-10-24 17:56:52.217][000000010.210] I/user.FOTA_FILE 初始化FOTA...
+[2025-10-24 17:56:52.224][000000010.226] I/user.FOTA_FILE 等待底层准备...
+[2025-10-24 17:56:52.234][000000010.226] I/user.FOTA_FILE 底层准备就绪
+[2025-10-24 17:56:52.242][000000010.227] I/user.FOTA_FILE 开始读取升级文件: /update.bin
+[2025-10-24 17:56:52.251][000000010.230] I/fota write common data
+[2025-10-24 17:56:52.309][000000010.311] I/fota common data done, now checking 0
+[2025-10-24 17:56:52.317][000000010.312] I/fota common data md5 ok
+[2025-10-24 17:56:52.325][000000010.312] I/fota only common data
+[2025-10-24 17:56:52.358][000000010.362] I/fota fota type 0 ok!, wait reboot
+[2025-10-24 17:56:52.370][000000010.362] I/user.FOTA_FILE 升级文件写入flash中的fota分区结果 true true 0
+[2025-10-24 17:56:52.380][000000010.363] I/user.FOTA_FILE 结束写入fota分区...
+[2025-10-24 17:56:52.389][000000010.363] I/user.FOTA_FILE 写入fota分区状态 结果: true 完成: true
+[2025-10-24 17:56:52.402][000000010.363] I/user.FOTA_FILE 升级成功,准备重启设备
+[2025-10-24 17:56:53.199][000000011.205] I/user.fota version 1.0.0
+[2025-10-24 17:56:54.197][000000012.205] I/user.fota version 1.0.0
+[2025-10-24 17:56:55.575][000000000.000] main_entry 708:SDK base line V017_pb18.002
+[2025-10-24 17:56:55.589][000000000.008] am_service_init 1154:Air780EPM_A11
+[2025-10-24 17:56:55.604][000000000.008] am_get_chip_type 635:6bef6,19,24,c7,0,EC718
+[2025-10-24 17:56:55.618][000000000.049] bsp_user_init_io 312:io volt 3.3v 21
+[2025-10-24 17:56:55.630][000000000.050] BSP_CustomInit 558:hardfault mode init 4
+[2025-10-24 17:56:55.643][000000000.050] Uart_ChangeBR 1338:uart0, 6000000 6028985 26000000 69
+[2025-10-24 17:56:55.654][000000000.072] I/pm poweron: Power/Reset
+[2025-10-24 17:56:55.668][000000000.181] self_info 127:model Air780EPM_A11 imei 862419074066563
+[2025-10-24 17:56:55.682][000000000.181] self_info 129:firmware[1] BASIC
+[2025-10-24 17:56:55.692][000000000.181] self_info 131:zone(kbytes) fs 168 script 256
+[2025-10-24 17:56:55.704][000000000.181] I/main LuatOS@Air780EPM base 25.03 bsp V2016 32bit
+[2025-10-24 17:56:55.720][000000000.181] I/main ROM Build: Oct  9 2025 21:32:15
+[2025-10-24 17:56:55.734][000000000.183] W/pins /luadb/pins_AIR780EPM.json not exist!!
+[2025-10-24 17:56:55.749][000000000.186] D/main loadlibs luavm 1048568 14888 14888
+[2025-10-24 17:56:55.762][000000000.186] D/main loadlibs sys   2375432 53100 58844
+[2025-10-24 17:56:55.772][000000000.186] D/main loadlibs psram 2375432 53184 58844
+[2025-10-24 17:56:56.105][000000001.206] I/user.fota version 1.0.1
+[2025-10-24 17:56:56.118][000000001.206] I/user.fota123456789
+[2025-10-24 17:56:57.105][000000002.206] I/user.fota version 1.0.1
+[2025-10-24 17:56:57.111][000000002.207] I/user.fota123456789
+[2025-10-24 17:56:58.108][000000003.207] I/user.fota version 1.0.1
+[2025-10-24 17:56:58.116][000000003.208] I/user.fota123456789
+```
+
+### 方式2:串口分段升级
+
+1、搭建好演示硬件环境
+
+2、修改demo脚本代码,确保`main.lua`中已注释`require("fota_file")`,取消`require("fota_uart")`的注释
+
+3、使用Luatools制作升级包,先把新旧版本分别生成量产文件,然后再制作升级包,工具上栏 luatOS->固件工具->差分包/整包升级包制作,将制作好的升级包放在main.py同级目录下
+
+4、Luatools烧录内核固件和修改前的demo脚本代码,烧录成功后,自动开机运行。
+
+5、确认设备连接到电脑的串口(虚拟USB串口)
+
+6、按一下核心板上的Powerkey键,然后运行Python脚本发送升级包:
+
+```lua
+   python main.py
+```
+
+7、脚本会自动寻找设备虚拟串口,发送升级命令并传输`fota_uart.bin`文件
+
+8、设备接收并验证升级包,升级成功后会自动重启
+
+9、可以看到如下日志:
+
+串口分段升级日志解读:
+
+1. USB虚拟串口连接,收到#FOTA起始指令
+
+2. 开始分段接收升级包,每次256字节,累计5751字节
+
+3. 所有数据包写入成功,MD5校验通过
+
+4. 升级完成,重启
+
+5. 重启后新版本1.0.2运行,新增日志确认升级成功
+
+结果:串口FOTA升级完全成功,版本从1.0.0升级到1.0.2。
+```lua
+[2025-10-24 19:04:42.229][000000130.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:43.235][000000131.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:44.239][000000132.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:44.896][000000132.888] luat_usb_recv_cb 447:usb serial connected
+[2025-10-24 19:04:44.909][000000132.889] I/user.uart 收到数据 6 累计 6
+[2025-10-24 19:04:44.921][000000132.891] I/user.fota 检测到fota起始标记,进入FOTA状态 #FOTA
+
+[2025-10-24 19:04:45.238][000000133.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:45.916][000000133.894] I/user.uart 收到数据 256 累计 262
+[2025-10-24 19:04:45.931][000000133.895] I/user.准备写入fota包 256 累计写入 256
+[2025-10-24 19:04:45.939][000000133.896] I/fota write common data
+[2025-10-24 19:04:45.957][000000133.896] D/user.fota.run true false 1
+[2025-10-24 19:04:45.968][000000133.896] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:46.227][000000134.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:46.929][000000134.906] I/user.uart 收到数据 256 累计 518
+[2025-10-24 19:04:46.942][000000134.907] I/user.准备写入fota包 256 累计写入 512
+[2025-10-24 19:04:46.953][000000134.908] D/user.fota.run true false 1
+[2025-10-24 19:04:46.964][000000134.908] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:47.227][000000135.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:47.940][000000135.917] I/user.uart 收到数据 256 累计 774
+[2025-10-24 19:04:47.950][000000135.918] I/user.准备写入fota包 256 累计写入 768
+[2025-10-24 19:04:47.963][000000135.919] D/user.fota.run true false 1
+[2025-10-24 19:04:47.971][000000135.919] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:48.238][000000136.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:48.956][000000136.933] I/user.uart 收到数据 256 累计 1030
+[2025-10-24 19:04:48.965][000000136.933] I/user.准备写入fota包 256 累计写入 1024
+[2025-10-24 19:04:48.976][000000136.934] D/user.fota.run true false 1
+[2025-10-24 19:04:48.986][000000136.934] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:49.237][000000137.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:49.968][000000137.945] I/user.uart 收到数据 256 累计 1286
+[2025-10-24 19:04:49.977][000000137.946] I/user.准备写入fota包 256 累计写入 1280
+[2025-10-24 19:04:49.987][000000137.946] D/user.fota.run true false 1
+[2025-10-24 19:04:49.995][000000137.947] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:50.231][000000138.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:50.981][000000138.957] I/user.uart 收到数据 256 累计 1542
+[2025-10-24 19:04:50.990][000000138.958] I/user.准备写入fota包 256 累计写入 1536
+[2025-10-24 19:04:51.008][000000138.958] D/user.fota.run true false 1
+[2025-10-24 19:04:51.016][000000138.959] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:51.229][000000139.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:51.992][000000139.969] I/user.uart 收到数据 256 累计 1798
+[2025-10-24 19:04:52.002][000000139.970] I/user.准备写入fota包 256 累计写入 1792
+[2025-10-24 19:04:52.013][000000139.970] D/user.fota.run true false 1
+[2025-10-24 19:04:52.020][000000139.971] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:52.228][000000140.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:53.007][000000140.984] I/user.uart 收到数据 256 累计 2054
+[2025-10-24 19:04:53.019][000000140.985] I/user.准备写入fota包 256 累计写入 2048
+[2025-10-24 19:04:53.029][000000140.985] D/user.fota.run true false 1
+[2025-10-24 19:04:53.037][000000140.985] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:53.226][000000141.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:54.005][000000141.997] I/user.uart 收到数据 256 累计 2310
+[2025-10-24 19:04:54.019][000000141.998] I/user.准备写入fota包 256 累计写入 2304
+[2025-10-24 19:04:54.034][000000141.998] D/user.fota.run true false 1
+[2025-10-24 19:04:54.044][000000141.999] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:54.225][000000142.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:55.022][000000142.998] I/user.uart 收到数据 256 累计 2566
+[2025-10-24 19:04:55.032][000000142.999] I/user.准备写入fota包 256 累计写入 2560
+[2025-10-24 19:04:55.042][000000143.000] D/user.fota.run true false 1
+[2025-10-24 19:04:55.050][000000143.000] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:55.240][000000143.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:56.035][000000144.012] I/user.uart 收到数据 256 累计 2822
+[2025-10-24 19:04:56.044][000000144.013] I/user.准备写入fota包 256 累计写入 2816
+[2025-10-24 19:04:56.054][000000144.013] D/user.fota.run true false 1
+[2025-10-24 19:04:56.060][000000144.014] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:56.237][000000144.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:57.048][000000145.024] I/user.uart 收到数据 256 累计 3078
+[2025-10-24 19:04:57.063][000000145.025] I/user.准备写入fota包 256 累计写入 3072
+[2025-10-24 19:04:57.076][000000145.026] D/user.fota.run true false 1
+[2025-10-24 19:04:57.088][000000145.026] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:57.235][000000145.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:58.057][000000146.034] I/user.uart 收到数据 256 累计 3334
+[2025-10-24 19:04:58.065][000000146.035] I/user.准备写入fota包 256 累计写入 3328
+[2025-10-24 19:04:58.076][000000146.035] D/user.fota.run true false 1
+[2025-10-24 19:04:58.085][000000146.035] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:58.228][000000146.217] I/user.fota version 1.0.0
+[2025-10-24 19:04:59.070][000000147.048] I/user.uart 收到数据 256 累计 3590
+[2025-10-24 19:04:59.079][000000147.049] I/user.准备写入fota包 256 累计写入 3584
+[2025-10-24 19:04:59.085][000000147.049] D/user.fota.run true false 1
+[2025-10-24 19:04:59.094][000000147.049] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:04:59.227][000000147.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:00.083][000000148.061] I/user.uart 收到数据 256 累计 3846
+[2025-10-24 19:05:00.096][000000148.062] I/user.准备写入fota包 256 累计写入 3840
+[2025-10-24 19:05:00.109][000000148.062] D/user.fota.run true false 1
+[2025-10-24 19:05:00.121][000000148.062] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:00.240][000000148.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:01.093][000000149.070] I/user.uart 收到数据 256 累计 4102
+[2025-10-24 19:05:01.102][000000149.071] I/user.准备写入fota包 256 累计写入 4096
+[2025-10-24 19:05:01.112][000000149.071] D/user.fota.run true false 1
+[2025-10-24 19:05:01.119][000000149.071] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:01.233][000000149.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:02.090][000000150.082] I/user.uart 收到数据 256 累计 4358
+[2025-10-24 19:05:02.102][000000150.083] I/user.准备写入fota包 256 累计写入 4352
+[2025-10-24 19:05:02.136][000000150.116] D/user.fota.run true false 1
+[2025-10-24 19:05:02.144][000000150.116] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:02.229][000000150.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:03.117][000000151.094] I/user.uart 收到数据 256 累计 4614
+[2025-10-24 19:05:03.144][000000151.095] I/user.准备写入fota包 256 累计写入 4608
+[2025-10-24 19:05:03.153][000000151.095] D/user.fota.run true false 1
+[2025-10-24 19:05:03.162][000000151.096] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:03.226][000000151.216] I/user.fota version 1.0.0
+[2025-10-24 19:05:04.118][000000152.095] I/user.uart 收到数据 256 累计 4870
+[2025-10-24 19:05:04.128][000000152.096] I/user.准备写入fota包 256 累计写入 4864
+[2025-10-24 19:05:04.138][000000152.097] D/user.fota.run true false 1
+[2025-10-24 19:05:04.152][000000152.097] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:04.227][000000152.216] I/user.fota version 1.0.0
+[2025-10-24 19:05:05.133][000000153.108] I/user.uart 收到数据 256 累计 5126
+[2025-10-24 19:05:05.147][000000153.109] I/user.准备写入fota包 256 累计写入 5120
+[2025-10-24 19:05:05.167][000000153.109] D/user.fota.run true false 1
+[2025-10-24 19:05:05.179][000000153.110] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:05.226][000000153.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:06.126][000000154.117] I/user.uart 收到数据 256 累计 5382
+[2025-10-24 19:05:06.138][000000154.118] I/user.准备写入fota包 256 累计写入 5376
+[2025-10-24 19:05:06.150][000000154.119] D/user.fota.run true false 1
+[2025-10-24 19:05:06.161][000000154.119] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:06.233][000000154.216] I/user.fota version 1.0.0
+[2025-10-24 19:05:07.155][000000155.132] I/user.uart 收到数据 256 累计 5638
+[2025-10-24 19:05:07.167][000000155.133] I/user.准备写入fota包 256 累计写入 5632
+[2025-10-24 19:05:07.181][000000155.133] D/user.fota.run true false 1
+[2025-10-24 19:05:07.192][000000155.133] I/user.fota 单包写入完成 256 等待下一个包
+[2025-10-24 19:05:07.234][000000155.216] I/user.fota version 1.0.0
+[2025-10-24 19:05:08.170][000000156.147] I/user.uart 收到数据 113 累计 5751
+[2025-10-24 19:05:08.189][000000156.148] I/user.准备写入fota包 113 累计写入 5745
+[2025-10-24 19:05:08.209][000000156.177] I/fota common data done, now checking 0
+[2025-10-24 19:05:08.232][000000156.179] I/fota common data md5 ok
+[2025-10-24 19:05:08.256][000000156.179] I/fota only common data
+[2025-10-24 19:05:08.268][000000156.213] I/fota fota type 0 ok!, wait reboot
+[2025-10-24 19:05:08.278][000000156.213] D/user.fota.run true true 0
+[2025-10-24 19:05:08.287][000000156.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:08.324][000000156.314] I/user.fota 已完成,1s后重启
+[2025-10-24 19:05:09.227][000000157.217] I/user.fota version 1.0.0
+[2025-10-24 19:05:10.822][000000000.000] main_entry 708:SDK base line V017_pb18.002
+[2025-10-24 19:05:10.835][000000000.008] am_service_init 1154:Air780EPM_A11
+[2025-10-24 19:05:10.847][000000000.008] am_get_chip_type 635:6bef6,19,24,c7,0,EC718
+[2025-10-24 19:05:10.858][000000000.051] bsp_user_init_io 312:io volt 3.3v 21
+[2025-10-24 19:05:10.870][000000000.051] BSP_CustomInit 558:hardfault mode init 4
+[2025-10-24 19:05:10.879][000000000.052] Uart_ChangeBR 1338:uart0, 6000000 6028985 26000000 69
+[2025-10-24 19:05:10.894][000000000.073] I/pm poweron: Power/Reset
+[2025-10-24 19:05:10.908][000000000.185] self_info 127:model Air780EPM_A11 imei 862419074066563
+[2025-10-24 19:05:10.920][000000000.186] self_info 129:firmware[1] BASIC
+[2025-10-24 19:05:10.935][000000000.186] self_info 131:zone(kbytes) fs 168 script 256
+[2025-10-24 19:05:10.946][000000000.186] I/main LuatOS@Air780EPM base 25.03 bsp V2016 32bit
+[2025-10-24 19:05:10.956][000000000.186] I/main ROM Build: Oct  9 2025 21:32:15
+[2025-10-24 19:05:10.972][000000000.188] W/pins /luadb/pins_AIR780EPM.json not exist!!
+[2025-10-24 19:05:10.987][000000000.191] D/main loadlibs luavm 1048568 14888 14888
+[2025-10-24 19:05:11.002][000000000.191] D/main loadlibs sys   2375432 53100 58844
+[2025-10-24 19:05:11.018][000000000.191] D/main loadlibs psram 2375432 53184 58844
+[2025-10-24 19:05:11.257][000000001.213] I/user.fota version 1.0.2
+[2025-10-24 19:05:11.273][000000001.213] I/user.fota1111122222222222
+[2025-10-24 19:05:12.083][000000002.214] I/user.fota version 1.0.2
+[2025-10-24 19:05:12.094][000000002.214] I/user.fota1111122222222222
+[2025-10-24 19:05:13.079][000000003.215] I/user.fota version 1.0.2
+[2025-10-24 19:05:13.088][000000003.215] I/user.fota1111122222222222
+
+```
+
+main.py 日志:
+```lua
+D:\gitee_hz\fota>python main.py
+COM59
+设备响应 b'#FOTA RDY\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 256
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 113
+设备响应 b'#FOTA OK\n'
+发送完毕,退出
+```
+
+

+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/README.md → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/README.md


+ 2 - 2
module/Air780EPM/demo/fota2/iot_server/air_srv_fota.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/air_srv_fota.lua

@@ -43,7 +43,7 @@ local fota_running = false
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     -- fota结束,无论成功还是失败,都释放fota_running标志
@@ -65,7 +65,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/main.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/main.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/netdrv/netdrv_4g.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_4g.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/netdrv/netdrv_eth_spi.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_eth_spi.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/netdrv/netdrv_multiple.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_multiple.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/netdrv_device.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv_device.lua


+ 2 - 2
module/Air780EPM/demo/fota2/iot_server/psm_power_fota.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/psm_power_fota.lua

@@ -33,7 +33,7 @@ libfota2 = require "libfota2"
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     --升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
@@ -54,7 +54,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/tcp_iot/tcp_iot_main.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_main.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/tcp_iot/tcp_iot_receiver.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_receiver.lua


+ 0 - 0
module/Air780EPM/demo/fota2/iot_server/tcp_iot/tcp_iot_sender.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_sender.lua


+ 3 - 3
module/Air780EPM/demo/fota2/iot_server/update.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/iot_server/update.lua

@@ -13,7 +13,7 @@
 5、在升级结果的回调函数中,根据升级结果进行处理;
 ]]
 -- 使用合宙iot平台时需要这个参数
-PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
+PRODUCT_KEY = "X1zBmxSd1H2Gy69DtAyNytmUe7dudGXm" -- 到 iot.openluat.com 创建项目,获取正确的项目id
 
 libfota2 = require "libfota2"
 
@@ -36,7 +36,7 @@ sys.timerLoopStart(get_version, 3000)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -55,7 +55,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 0 - 0
module/Air780EPM/demo/fota2/self_server/README.md → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/README.md


+ 3 - 3
module/Air780EPM/demo/fota2/self_server/customer_srv_fota.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/customer_srv_fota.lua

@@ -37,7 +37,7 @@ local fota_running = false
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     -- fota结束,无论成功还是失败,都释放fota_running标志
@@ -59,7 +59,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end
@@ -97,7 +97,7 @@ local opts = {
 
     -- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
     version = ""
-    -- 其他更多参数, 请查阅libfota2的文档 https://docs.openluat.com/osapi/ext/libfota2/
+    -- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
 }
 
 

+ 0 - 0
module/Air780EPM/demo/fota2/self_server/main.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/main.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/netdrv/netdrv_4g.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_4g.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/netdrv/netdrv_eth_spi.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_eth_spi.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/netdrv/netdrv_multiple.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_multiple.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/netdrv_device.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv_device.lua


+ 4 - 4
module/Air780EPM/demo/fota2/self_server/psm_power_fota.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/psm_power_fota.lua

@@ -29,7 +29,7 @@ libfota2 = require "libfota2"
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     --升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
@@ -50,7 +50,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end
@@ -60,7 +60,7 @@ end
 
 -- 使用合宙iot平台进行升级, 支持自定义参数, 也可以不配置,如果要配置参数可以参考此链接https://docs.openluat.com/osapi/ext/libfota2/
 local opts = {
-    url = "###http://cdn.openluat-backend.openluat.com/upgrade_firmware/fotademo_2008.001.001_LuatOS-SoC_Air8000.bin",
+    url = "###http://cdn.openluat-backend.openluat.com/upgrade_firmware/fotademo_2008.001.001_LuatOS-SoC_Air8000.bin_20250623184110381812",
     -- 合宙IOT平台的默认升级URL, 不填就是这个默认值
     -- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
     -- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
@@ -73,7 +73,7 @@ local opts = {
 
     -- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
     -- version = ""
-    -- 其他更多参数, 请查阅libfota2的文档 https://docs.openluat.com/osapi/ext/libfota2/
+    -- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
 }
 
 

+ 0 - 0
module/Air780EPM/demo/fota2/self_server/tcp_self_server/tcp_self_main.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_main.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/tcp_self_server/tcp_self_receiver.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_receiver.lua


+ 0 - 0
module/Air780EPM/demo/fota2/self_server/tcp_self_server/tcp_self_sender.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_sender.lua


+ 4 - 4
module/Air780EPM/demo/fota2/self_server/update.lua → module/Air780EPM/demo/fota/fota2(使用libfota2扩展库)/self_server/update.lua

@@ -33,7 +33,7 @@ sys.timerLoopStart(get_version, 3000)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
+--   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -52,7 +52,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
+        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
     else
         log.info("不是上面几种情况 ret为", ret)
     end
@@ -76,7 +76,7 @@ end
 -- 13. opts.body string 额外添加的请求body,默认不需要
 ]]
 local opts = {
-    url = "###http://cdn.openluat-backend.openluat.com/upgrade_firmware/fotademo_2008.001.001_LuatOS-SoC_Air8000.bin",
+    url = "###http://cdn.openluat-backend.openluat.com/upgrade_firmware/fotademo_2008.001.001_LuatOS-SoC_Air8000.bin_20250623184110381812",
     -- 合宙IOT平台的默认升级URL, 不填就是这个默认值
     -- 如果是自建的OTA服务器, 则需要填写正确的URL, 例如 http://192.168.1.5:8000/update
     -- 如果自建OTA服务器,且url包含全部参数,不需要额外添加参数, 请在url前面添加 ###
@@ -89,7 +89,7 @@ local opts = {
 
     -- 请求的版本号, 合宙IOT有一套版本号体系,不传就是合宙规则, 自建服务器的话当然是自行约定版本号了
     -- version = ""
-    -- 其他更多参数, 请查阅libfota2的文档 https://docs.openluat.com/osapi/ext/libfota2/
+    -- 其他更多参数, 请查阅libfota2的文档 https://wiki.luatos.com/api/libs/libfota2.html
 }
 
 function fota_task_func()

+ 127 - 0
module/Air780EPM/demo/fota/readme.md

@@ -0,0 +1,127 @@
+# FOTA 与 FOTA2 选择指南
+
+## 核心区别总结
+
+### fota(底层核心库)
+
+定位: 基础升级,提供最核心的固件写入能力
+
+**核心能力:**
+
+支持两种写入方式:fota.run() 分段写入 和 fota.file() 文件直接升级
+
+支持内部存储和外部SPI Flash
+
+提供完整的升级流程控制:init → wait → run/file → isDone → finish
+
+代码特点:
+
+```lua
+-- 需要手动控制每个步骤
+fota.init()
+while not fota.wait() do sys.wait(100) end
+fota.run(buf)  -- 或 fota.file("/update.bin")
+-- 自行检查状态和重启
+```
+
+### fota2(libfota2扩展库)
+
+定位: 完整的远程升级解决方案,开箱即用
+
+**核心能力:**
+
+自动处理HTTP/HTTPS网络下载
+
+支持合宙IoT平台和自建服务器
+
+内置版本检查、下载、验证全流程
+
+提供详细错误码和回调函数
+
+代码特点:
+
+```lua
+-- 一行代码完成升级
+local function fota_cb(ret)
+    if ret == 0 then
+        log.info("升级包下载成功,重启模块")
+        rtos.reboot()
+    end
+end
+libfota2.request(fota_cb, opts)
+```
+
+## 适用场景推荐
+
+### 选择 fota 的情况:
+
+#### 需要自定义升级数据源
+
+通过串口接收升级包
+
+通过MQTT、TCP等自定义协议传输
+
+从SD卡、U盘等外部存储读取
+
+#### 对升级流程有特殊控制需求
+
+需要在升级前后执行特定操作
+
+需要精细控制数据写入时机
+
+需要自定义进度监控逻辑
+
+#### 资源极度受限环境
+
+设备存储空间极小,内存紧张,无法加载额外库
+
+#### 开发测试阶段
+
+需要调试升级过程的每个环节
+
+需要验证自定义升级方案
+
+### 选择 fota2 的情况:
+
+#### 标准的HTTP远程升级
+
+从服务器下载升级包
+
+使用合宙IoT平台服务
+
+需要HTTPS安全下载
+
+#### 希望快速实现升级功能
+
+不想处理网络下载细节
+
+需要自动版本检查
+
+希望简单的错误处理
+
+#### 生产环境部署
+
+需要稳定的远程升级方案
+
+需要详细的升级状态反馈
+
+支持定时自动检查更新
+
+
+
+## 实际选择建议
+
+### 新手用户 → 直接选择 fota2
+接口简单,学习成本低
+
+内置完整错误处理
+
+适合大多数物联网应用场景
+
+### 高级用户 → 根据需求选择
+标准网络升级 → fota2
+
+自定义数据传输 → fota + 自定义逻辑
+
+
+一句话总结:**用 fota2 省心省力,用 fota 自由灵活**