ソースを参照

Merge branch 'master' of https://gitee.com/openLuat/LuatOS

江访 2 ヶ月 前
コミット
13555c81ce
44 ファイル変更3365 行追加151 行削除
  1. 434 0
      module/Air8000/demo/fota/ble_fota/ble_fota_main.lua
  2. 258 0
      module/Air8101/demo/fota/ble_fota/ble_file_fota.lua
  3. BIN
      module/Air8101/demo/fota/ble_fota/ble_fota.bin
  4. 246 0
      module/Air8101/demo/fota/ble_fota/ble_fota_tool.py
  5. 236 0
      module/Air8101/demo/fota/ble_fota/ble_main.lua
  6. 251 0
      module/Air8101/demo/fota/ble_fota/ble_packet_fota.lua
  7. 92 0
      module/Air8101/demo/fota/ble_fota/main.lua
  8. 717 0
      module/Air8101/demo/fota/ble_fota/readme.md
  9. 92 0
      module/Air8101/demo/fota/fota(使用fota核心库)/fota_file.lua
  10. BIN
      module/Air8101/demo/fota/fota(使用fota核心库)/fota_uart.bin
  11. 212 0
      module/Air8101/demo/fota/fota(使用fota核心库)/fota_uart.lua
  12. 96 0
      module/Air8101/demo/fota/fota(使用fota核心库)/main.lua
  13. 42 0
      module/Air8101/demo/fota/fota(使用fota核心库)/main.py
  14. 542 0
      module/Air8101/demo/fota/fota(使用fota核心库)/readme.md
  15. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/README.md
  16. 3 3
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/air_srv_fota.lua
  17. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/main.lua
  18. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_4g.lua
  19. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_eth_rmii.lua
  20. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_eth_spi.lua
  21. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_multiple.lua
  22. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv/netdrv_wifi.lua
  23. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/netdrv_device.lua
  24. 3 3
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/psm_power_fota.lua
  25. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_main.lua
  26. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_receiver.lua
  27. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_sender.lua
  28. 3 3
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/update.lua
  29. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/README.md
  30. 3 3
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/customer_srv_fota.lua
  31. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/main.lua
  32. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_4g.lua
  33. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_eth_rmii.lua
  34. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_eth_spi.lua
  35. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_multiple.lua
  36. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv/netdrv_wifi.lua
  37. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/netdrv_device.lua
  38. 4 4
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/psm_power_fota.lua
  39. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_main.lua
  40. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_receiver.lua
  41. 0 0
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_sender.lua
  42. 4 4
      module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/update.lua
  43. 0 131
      module/Air8101/demo/fota/main.lua
  44. 127 0
      module/Air8101/demo/fota/readme.md

+ 434 - 0
module/Air8000/demo/fota/ble_fota/ble_fota_main.lua

@@ -0,0 +1,434 @@
+--[[
+@module  ble_fota_main
+@summary 蓝牙FOTA升级功能模块
+@version 1.0
+@date    2025.01.20
+@author  孟伟
+@usage
+-- 蓝牙FOTA升级功能
+-- 提供通过蓝牙低功耗(BLE)接收升级包数据进行固件升级的功能
+
+用法:
+1. 先把脚本和固件烧录到Air8000模块中,并确认设备正常启动
+2. 模块启动后会自动开启BLE广播,广播名称为"Air8000_FOTA"
+3. 在电脑端操作:运行ble_test.py脚本连接设备并发送升级固件
+   注意:确保升级文件名为正确格式,并且与ble_test.py在同一目录下
+4. 观察日志输出确认升级进度
+5. 模块接收并验证固件成功后,会自动重启并应用新固件
+
+BLE通讯过程说明
+蓝牙FOTA升级通过BLE特征值进行命令控制和数据传输:
+协议流程:
+1. 上位机通过BLE扫描并连接名为"Air8000_FOTA"的设备
+2. 上位机向命令特征值(F001)发送开始升级命令(0x01)和固件大小
+3. 设备初始化FOTA功能并准备接收数据
+4. 上位机向数据特征值(F002)分包发送固件数据
+5. 设备接收并保存数据到临时文件
+6. 上位机发送结束升级命令(0x02)
+7. 设备验证固件完整性并执行FOTA升级流程
+8. 升级成功后设备自动重启
+
+注意:
+- 本demo使用特定的服务UUID(F000)和特征值UUID(F001/F002)进行通信
+- 升级过程中如果连接断开,设备会自动终止升级并清理临时文件
+- 升级包大小不能超过设备的存储空间
+- 本文件没有对外接口,直接在main.lua中require "ble_fota_main"就可以加载运行;
+]]
+
+-- 配置参数
+local config = {
+    device_name = "Air8000_FOTA", -- 设备广播名称
+    service_uuid = "F000",        -- FOTA服务UUID(短格式)
+    char_uuid_cmd = "F001",       -- 命令特征值UUID
+    char_uuid_data = "F002",      -- 数据特征值UUID
+    max_packet_size = 20          -- BLE数据包最大长度(字节)
+}
+
+-- 升级状态管理
+local upgrade_state = {
+    is_upgrading = false,          -- 是否正在升级
+    ble_connected = false,         -- 是否已连接蓝牙设备
+    total_size = 0,                -- 总文件大小(字节)
+    received_size = 0,             -- 已接收大小(字节)
+    upgrade_file = "/ble_fota.bin" -- 临时升级文件路径
+}
+
+-- GATT服务数据库定义
+-- 这里定义了BLE设备提供的服务和特征值
+-- GATT数据库定义
+local att_db = {
+    string.fromHex(config.service_uuid), -- Service UUID
+    {
+        string.fromHex(config.char_uuid_cmd),
+        ble.WRITE | ble.WRITE_CMD
+    },
+    {
+        string.fromHex(config.char_uuid_data),
+        ble.WRITE | ble.WRITE_CMD
+    },
+    -- Characteristic 3: Status (Notify + Read)
+    -- {
+    --     string.fromHex(config.char_uuid_status),
+    --     ble.NOTIFY | ble.READ
+    -- }
+}
+
+-- 处理FOTA命令
+-- @param cmd_data 命令数据,格式:[命令码(1字节)] 或 [命令码(1字节) + 文件大小(4字节)]
+local function handle_command(cmd_data)
+    log.info("FOTA_CMD", "收到命令数据:", cmd_data:toHex(), "长度:", #cmd_data)
+
+    -- 检查命令数据是否有效
+    if #cmd_data < 1 then
+        log.error("FOTA_CMD", "命令数据为空")
+        return
+    end
+
+    -- 解析命令码(第一个字节)
+    local cmd = cmd_data:byte(1)
+    log.info("FOTA_CMD", "解析命令码:", cmd, string.format("(0x%02X)", cmd))
+
+    -- 命令0x01:开始升级
+    if cmd == 0x01 then
+        log.info("FOTA_CMD", "处理开始升级命令")
+
+        -- 检查命令格式:需要至少5字节(1字节命令码 + 4字节文件大小)
+        if #cmd_data >= 5 then
+            -- 解析文件大小(小端序,从第2字节开始)
+            local total_size = string.unpack("<I4", cmd_data, 2)
+            log.info("FOTA_CMD", "文件总大小:", total_size, "字节")
+
+            -- 初始化FOTA子系统
+            log.info("FOTA_CMD", "初始化FOTA子系统...")
+            if fota.init() then
+                log.info("FOTA_CMD", "FOTA初始化成功")
+
+                -- 等待FOTA底层准备就绪
+                log.info("FOTA_CMD", "等待FOTA底层准备...")
+                if fota.wait() then
+                    log.info("FOTA_CMD", "FOTA底层准备就绪")
+
+                    -- 删除旧的临时文件(如果存在)
+                    if os.remove(upgrade_state.upgrade_file) then
+                        log.info("FOTA_CMD", "已清理旧临时文件")
+                    end
+
+                    -- 更新升级状态
+                    upgrade_state.is_upgrading = true
+                    upgrade_state.total_size = total_size
+                    upgrade_state.received_size = 0
+
+                    log.info("FOTA_CMD", "升级状态已设置",
+                        "总大小:", upgrade_state.total_size,
+                        "临时文件:", upgrade_state.upgrade_file)
+                    log.info("FOTA_CMD", "准备接收固件数据...")
+                else
+                    log.error("FOTA_CMD", "FOTA底层准备失败")
+                    fota.finish(false)
+                end
+            else
+                log.error("FOTA_CMD", "FOTA初始化失败")
+            end
+        else
+            log.error("FOTA_CMD", "开始命令格式错误,长度不足")
+        end
+
+        -- 命令0x02:结束升级
+    elseif cmd == 0x02 then
+        log.info("FOTA_CMD", "处理结束升级命令")
+
+        -- 检查是否处于升级状态
+        if not upgrade_state.is_upgrading then
+            log.warn("FOTA_CMD", "未处于升级状态,忽略结束命令")
+            return
+        end
+
+        -- 验证文件完整性
+        log.info("FOTA_CMD", "验证文件完整性...")
+        log.info("FOTA_CMD", "已接收:", upgrade_state.received_size, "字节")
+        log.info("FOTA_CMD", "应接收:", upgrade_state.total_size, "字节")
+
+        if upgrade_state.received_size == upgrade_state.total_size then
+            log.info("FOTA_CMD", "文件完整性验证通过")
+
+            -- 执行FOTA升级
+            log.info("FOTA_CMD", "开始执行FOTA升级...")
+            local result, isDone = fota.file(upgrade_state.upgrade_file)
+            log.info("FOTA_CMD", "FOTA升级结果:", "result:", result, "isDone:", isDone)
+
+            if result and isDone then
+                log.info("FOTA_CMD", " FOTA升级成功!")
+
+                -- 延迟重启,给用户一些反应时间
+                log.info("FOTA_CMD", "2秒后设备将自动重启...")
+
+                -- 延迟2秒后重启设备
+                sys.timerStart(function()
+                    -- 完成FOTA流程并重启
+                    fota.finish(true)
+                    log.info("FOTA_CMD", "正在重启设备...")
+                    rtos.reboot()
+                end, 2000)
+            else
+                log.error("FOTA_CMD", "FOTA升级失败")
+            end
+        else
+            log.error("FOTA_CMD", "文件不完整,升级失败")
+        end
+
+        -- 清理升级状态(无论成功还是失败)
+        log.info("FOTA_CMD", "清理升级状态...")
+        upgrade_state.is_upgrading = false
+
+        -- 删除临时文件
+        if upgrade_state.upgrade_file then
+            if os.remove(upgrade_state.upgrade_file) then
+                log.info("FOTA_CMD", "已删除临时文件")
+            else
+                log.warn("FOTA_CMD", "删除临时文件失败")
+            end
+        end
+
+        -- 结束FOTA流程
+        fota.finish(false)
+        log.info("FOTA_CMD", "升级流程结束")
+    else
+        log.warn("FOTA_CMD", "未知命令码:", cmd, string.format("(0x%02X)", cmd))
+    end
+end
+
+-- 处理FOTA数据
+-- @param data 固件数据块
+local function handle_data(data)
+    log.info("FOTA_DATA", "收到数据包,长度:", #data, "字节")
+
+    -- 检查是否处于升级状态
+    if not upgrade_state.is_upgrading then
+        log.warn("FOTA_DATA", "未处于升级状态,忽略数据")
+        return
+    end
+
+    -- 保存数据到临时文件
+    log.info("FOTA_DATA", "写入文件:", upgrade_state.upgrade_file)
+    local file = io.open(upgrade_state.upgrade_file, "ab")
+    if file then
+        -- 写入数据
+        file:write(data)
+        file:close()
+
+        -- 更新接收状态
+        upgrade_state.received_size = upgrade_state.received_size + #data
+
+        -- 计算并显示进度
+        local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
+
+        -- 每50个数据包或完成时打印进度
+        if upgrade_state.received_size % (config.max_packet_size * 50) == 0 or
+            upgrade_state.received_size >= upgrade_state.total_size then
+            log.info("FOTA_DATA", "升级进度:", progress, "%",
+                "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
+        end
+
+        log.info("FOTA_DATA", "数据写入成功,当前总计:", upgrade_state.received_size, "字节")
+    else
+        log.error("FOTA_DATA", "打开文件失败:", upgrade_state.upgrade_file)
+
+        -- 文件操作失败,终止升级
+        upgrade_state.is_upgrading = false
+        fota.finish(false)
+    end
+end
+
+-- BLE事件回调函数
+-- @param ble_dev BLE设备对象
+-- @param event 事件类型
+-- @param param 事件参数
+local function ble_event_cb(ble_dev, event, param)
+    log.info("BLE_EVENT", "收到BLE事件:", event)
+
+    -- 根据LuatOS BLE事件枚举处理不同事件
+    if event == ble.EVENT_CONN then
+        -- 连接成功事件
+        log.info("BLE_EVENT", "设备已连接", "地址:", param.addr and param.addr:toHex() or "未知")
+        upgrade_state.ble_connected = true
+    elseif event == ble.EVENT_DISCONN then
+        -- 连接断开事件
+        log.info("BLE_EVENT", "设备已断开连接", "原因:", param.reason or "未知")
+        upgrade_state.ble_connected = false
+
+        -- 如果正在升级,连接断开则终止升级
+        if upgrade_state.is_upgrading then
+            log.error("BLE_EVENT", "升级过程中连接断开,终止升级")
+            upgrade_state.is_upgrading = false
+
+            -- 删除临时文件
+            if upgrade_state.upgrade_file then
+                os.remove(upgrade_state.upgrade_file)
+            end
+
+            -- 结束FOTA流程
+            fota.finish(false)
+        end
+    elseif event == ble.EVENT_WRITE then
+        -- 写入事件 - 这是关键事件!
+        log.info("BLE_EVENT", "处理写入事件")
+
+        -- 检查参数是否完整
+        if not param or not param.uuid_service or not param.uuid_characteristic or not param.data then
+            log.error("BLE_EVENT", "写入事件参数不完整")
+            return
+        end
+
+        -- 获取服务UUID和特征值UUID
+        local service_uuid = param.uuid_service:toHex()
+        local char_uuid = param.uuid_characteristic:toHex()
+        local data = param.data
+
+        log.info("BLE_EVENT", "服务UUID:", service_uuid)
+        log.info("BLE_EVENT", "特征值UUID:", char_uuid)
+        log.info("BLE_EVENT", "数据长度:", #data, "字节")
+
+        -- 简化的UUID匹配逻辑:检查UUID是否包含我们的短UUID
+        local is_service_match = string.find(service_uuid:lower(), "f000")
+        local is_cmd_match = string.find(char_uuid:lower(), "f001")
+        local is_data_match = string.find(char_uuid:lower(), "f002")
+
+        log.info("BLE_EVENT", "UUID匹配结果:",
+            "服务匹配:", is_service_match,
+            "命令匹配:", is_cmd_match,
+            "数据匹配:", is_data_match)
+
+        if is_service_match then
+            if is_cmd_match then
+                -- 命令特征值:处理FOTA命令
+                log.info("BLE_EVENT", "命令特征值匹配,处理命令")
+                handle_command(data)
+            elseif is_data_match then
+                -- 数据特征值:处理FOTA数据
+                log.info("BLE_EVENT", "数据特征值匹配,处理数据")
+                handle_data(data)
+            else
+                log.warn("BLE_EVENT", "未知的特征值UUID:", char_uuid)
+            end
+        else
+            log.warn("BLE_EVENT", "未知的服务UUID:", service_uuid)
+        end
+    elseif event == ble.EVENT_READ then
+        -- 读取事件 - 外围设备收到主设备读请求
+        log.info("BLE_EVENT", "处理读取事件")
+    elseif event == ble.EVENT_READ_VALUE then
+        -- 读取操作完成事件 - 中心设备读取特征值完成
+        log.info("BLE_EVENT", "读取操作完成", "数据:", param.data and param.data:toHex() or "无数据")
+    elseif event == ble.EVENT_SCAN_REPORT then
+        -- 扫描报告事件 - 中心设备扫描到其他BLE设备
+        log.info("BLE_EVENT", "扫描报告", "RSSI:", param.rssi, "地址:", param.adv_addr and param.adv_addr:toHex() or "未知")
+    elseif event == ble.EVENT_SCAN_STOP then
+        -- 扫描停止事件
+        log.info("BLE_EVENT", "扫描停止")
+    else
+        -- 其他事件
+        log.info("BLE_EVENT", "其他事件类型:", event)
+        if param then
+            -- 尝试打印参数的基本信息,避免直接打印table导致错误
+            if type(param) == "table" then
+                log.info("BLE_EVENT", "事件参数为table,包含字段:", #param)
+                for k, v in pairs(param) do
+                    if type(v) == "string" then
+                        log.info("BLE_EVENT", "参数字段:", k, "值:", v:toHex())
+                    else
+                        log.info("BLE_EVENT", "参数字段:", k, "类型:", type(v))
+                    end
+                end
+            else
+                log.info("BLE_EVENT", "事件参数类型:", type(param))
+            end
+        end
+    end
+end
+
+-- 初始化BLE功能
+-- @return boolean 初始化是否成功
+local function init_ble()
+    log.info("BLE_INIT", "开始初始化BLE...")
+
+
+    -- 初始化蓝牙核心
+    local bt_dev = bluetooth.init()
+    if not bt_dev then
+        log.error("BLE_INIT", "蓝牙核心初始化失败")
+        return false
+    end
+    log.info("BLE_INIT", "蓝牙核心初始化成功")
+
+    -- 初始化BLE功能
+    local ble_dev = bt_dev:ble(ble_event_cb)
+    if not ble_dev then
+        log.error("BLE_INIT", "BLE功能初始化失败")
+        return false
+    end
+    log.info("BLE_INIT", "BLE功能初始化成功")
+
+    -- 创建GATT服务
+    local gatt_result = ble_dev:gatt_create(att_db)
+    if not gatt_result then
+        log.error("BLE_INIT", "GATT服务创建失败")
+        return false
+    end
+    log.info("BLE_INIT", "GATT服务创建成功")
+
+    -- 配置广播数据
+    log.info("BLE_INIT", "配置广播数据...")
+    local adv_result = ble_dev:adv_create({
+        addr_mode = ble.PUBLIC,
+        channel_map = ble.CHNLS_ALL,
+        intv_min = 120,
+        intv_max = 120,
+        adv_data = {
+            { ble.FLAGS,               string.char(0x06) },  -- BLE标志
+            { ble.COMPLETE_LOCAL_NAME, config.device_name }, -- 设备名称
+        }
+    })
+
+    if not adv_result then
+        log.error("BLE_INIT", "广播配置失败")
+        return false
+    end
+    log.info("BLE_INIT", "广播配置成功")
+
+    -- 开始广播
+    ble_dev:adv_start()
+    log.info("BLE_INIT", " BLE广播已启动,设备名称:", config.device_name)
+
+    return true
+end
+
+-- 主任务函数
+sys.taskInit(function()
+
+    -- 主循环
+    while true do
+        log.info("MAIN", "尝试初始化BLE...")
+        -- 重置连接状态
+        upgrade_state.ble_connected = false
+        -- 初始化BLE
+        if init_ble() then
+            log.info("MAIN", "BLE初始化成功,进入主循环")
+
+            -- BLE运行状态维护循环
+            while true do
+                -- 等待5秒
+                sys.wait(5000)
+
+                if upgrade_state.is_upgrading then
+                    local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
+                    log.info("MAIN", "升级状态: 进行中",
+                        progress, "%",
+                        "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
+                end
+            end
+        else
+            log.error("MAIN", "BLE初始化失败,5秒后重试...")
+            sys.wait(5000) -- 等待5秒后重试
+        end
+    end
+end)

+ 258 - 0
module/Air8101/demo/fota/ble_fota/ble_file_fota.lua

@@ -0,0 +1,258 @@
+--[[
+@module  ble_file_fota
+@summary 蓝牙FOTA升级功能模块(文件写入方式)
+@version 1.0
+@date    2025.12.08
+@author  孟伟
+@usage
+-- 蓝牙FOTA升级功能(文件写入方式)
+-- 提供通过蓝牙低功耗(BLE)接收升级包数据进行固件升级的功能
+
+本文件为FOTA业务逻辑处理模块,核心业务逻辑为:
+1. 处理接收到的BLE写入请求数据
+2. 实现FOTA升级流程的控制
+3. 管理升级状态和文件操作
+
+本文件的对外接口有1个:
+1. ble_file_fota.proc(service_uuid, char_uuid, data): 处理接收到的BLE写入请求数据
+
+依赖模块:
+- ble_main: 用于提供BLE服务和事件处理
+]]
+
+local ble_file_fota = {}
+
+-- 升级状态管理
+local upgrade_state = {
+    is_upgrading = false,          -- 是否正在升级
+    total_size = 0,                -- 总文件大小(字节)
+    received_size = 0,             -- 已接收大小(字节)
+    upgrade_file = "/ble_fota.bin" -- 临时升级文件路径
+}
+
+-- 配置参数
+local config = {
+    service_uuid = "F000",   -- FOTA服务UUID(短格式)
+    char_uuid_cmd = "F001",  -- 命令特征值UUID
+    char_uuid_data = "F002", -- 数据特征值UUID
+    max_packet_size = 200    -- BLE数据包最大长度(字节)
+}
+local function ble_reboot()
+    -- 完成FOTA流程并重启
+    fota.finish(true)
+    log.info("FOTA_CMD", "正在重启设备...")
+    rtos.reboot()
+end
+-- 处理FOTA命令
+-- @param cmd_data 命令数据,格式:[命令码(1字节)] 或 [命令码(1字节) + 文件大小(4字节)]
+local function handle_command(cmd_data)
+    log.info("FOTA_CMD", "收到命令数据:", cmd_data:toHex(), "长度:", #cmd_data)
+
+    -- 检查命令数据是否有效
+    if #cmd_data < 1 then
+        log.error("FOTA_CMD", "命令数据为空")
+        return
+    end
+
+    -- 解析命令码(第一个字节)
+    local cmd = cmd_data:byte(1)
+    log.info("FOTA_CMD", "解析命令码:", cmd, string.format("(0x%02X)", cmd))
+
+    -- 命令0x01:开始升级
+    if cmd == 0x01 then
+        log.info("FOTA_CMD", "处理开始升级命令")
+
+        -- 检查命令格式:需要至少5字节(1字节命令码 + 4字节文件大小)
+        if #cmd_data >= 5 then
+            -- 解析文件大小(小端序,从第2字节开始)
+            local total_size = string.unpack("<I4", cmd_data, 2)
+            log.info("FOTA_CMD", "文件总大小:", total_size, "字节")
+
+            -- 初始化FOTA子系统
+            log.info("FOTA_CMD", "初始化FOTA子系统...")
+            if fota.init() then
+                log.info("FOTA_CMD", "FOTA初始化成功")
+
+                -- FOTA底层已准备就绪,无需等待
+                log.info("FOTA_CMD", "FOTA底层准备就绪")
+
+                -- 删除旧的临时文件(如果存在)
+                if os.remove(upgrade_state.upgrade_file) then
+                    log.info("FOTA_CMD", "已清理旧临时文件")
+                end
+
+                -- 更新升级状态
+                upgrade_state.is_upgrading = true
+                upgrade_state.total_size = total_size
+                upgrade_state.received_size = 0
+
+                log.info("FOTA_CMD", "升级状态已设置",
+                    "总大小:", upgrade_state.total_size,
+                    "临时文件:", upgrade_state.upgrade_file)
+                log.info("FOTA_CMD", "准备接收固件数据...")
+            else
+                log.error("FOTA_CMD", "FOTA初始化失败")
+            end
+        else
+            log.error("FOTA_CMD", "开始命令格式错误,长度不足")
+        end
+
+        -- 命令0x02:结束升级
+    elseif cmd == 0x02 then
+        log.info("FOTA_CMD", "处理结束升级命令")
+
+        -- 检查是否处于升级状态
+        if not upgrade_state.is_upgrading then
+            log.warn("FOTA_CMD", "未处于升级状态,忽略结束命令")
+            return
+        end
+
+        -- 验证文件完整性
+        log.info("FOTA_CMD", "验证文件完整性...")
+        log.info("FOTA_CMD", "已接收:", upgrade_state.received_size, "字节")
+        log.info("FOTA_CMD", "应接收:", upgrade_state.total_size, "字节")
+
+        if upgrade_state.received_size == upgrade_state.total_size then
+            log.info("FOTA_CMD", "文件完整性验证通过")
+
+            -- 执行FOTA升级
+            log.info("FOTA_CMD", "开始执行FOTA升级...")
+            local file_size = io.fileSize(upgrade_state.upgrade_file) -- 获取文件大小
+            log.info("FOTA_CMD", "文件大小:", file_size, "字节")
+            local result, isDone = fota.file(upgrade_state.upgrade_file)
+            log.info("FOTA_CMD", "FOTA升级结果:", "result:", result, "isDone:", isDone)
+
+            if result and isDone then
+                log.info("FOTA_CMD", " FOTA升级成功!")
+
+                -- 延迟重启,给用户一些反应时间
+                log.info("FOTA_CMD", "2秒后设备将自动重启...,重启后通过日志判断最终是否升级成功")
+
+                -- 延迟2秒后重启设备
+                sys.timerStart(ble_reboot, 2000)
+            else
+                log.error("FOTA_CMD", "FOTA升级失败")
+            end
+        else
+            log.error("FOTA_CMD", "文件不完整,升级失败")
+        end
+
+        -- 清理升级状态(无论成功还是失败)
+        log.info("FOTA_CMD", "清理升级状态...")
+        upgrade_state.is_upgrading = false
+
+        -- 删除临时文件
+        if upgrade_state.upgrade_file then
+            if os.remove(upgrade_state.upgrade_file) then
+                log.info("FOTA_CMD", "已删除临时文件")
+            else
+                log.warn("FOTA_CMD", "删除临时文件失败")
+            end
+        end
+
+        -- 结束FOTA流程
+        fota.finish(false)
+        log.info("FOTA_CMD", "升级流程结束")
+    else
+        log.warn("FOTA_CMD", "未知命令码:", cmd, string.format("(0x%02X)", cmd))
+    end
+end
+
+-- 处理FOTA数据
+-- @param data 固件数据块
+local function handle_data(data)
+    log.info("FOTA_DATA", "收到数据包,长度:", #data, "字节")
+
+    -- 检查是否处于升级状态
+    if not upgrade_state.is_upgrading then
+        log.warn("FOTA_DATA", "未处于升级状态,忽略数据")
+        return
+    end
+
+    -- 保存数据到临时文件
+    log.info("FOTA_DATA", "写入文件:", upgrade_state.upgrade_file)
+    local file = io.open(upgrade_state.upgrade_file, "ab")
+    if file then
+        -- 写入数据
+        file:write(data)
+        file:close()
+
+        -- 更新接收状态
+        upgrade_state.received_size = upgrade_state.received_size + #data
+
+        -- 计算并显示进度
+        local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
+
+        -- 每50个数据包或完成时打印进度
+        if upgrade_state.received_size % (config.max_packet_size * 50) == 0 or
+            upgrade_state.received_size >= upgrade_state.total_size then
+            log.info("FOTA_DATA", "升级进度:", progress, "%",
+                "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
+        end
+
+        log.info("FOTA_DATA", "数据写入成功,当前总计:", upgrade_state.received_size, "字节")
+    else
+        log.error("FOTA_DATA", "打开文件失败:", upgrade_state.upgrade_file)
+
+        -- 文件操作失败,终止升级
+        upgrade_state.is_upgrading = false
+        fota.finish(false)
+    end
+end
+
+-- 处理接收到的BLE写入请求数据
+-- @param service_uuid 服务UUID
+-- @param char_uuid 特征值UUID
+-- @param data 写入的数据
+function ble_file_fota.proc(service_uuid, char_uuid, data)
+    log.info("ble_file_fota", "处理写入数据", service_uuid, char_uuid, data:toHex())
+
+    -- 简化的UUID匹配逻辑:检查UUID是否包含我们的短UUID
+    local is_service_match = string.find(service_uuid:lower(), config.service_uuid:lower())
+    local is_cmd_match = string.find(char_uuid:lower(), config.char_uuid_cmd:lower())
+    local is_data_match = string.find(char_uuid:lower(), config.char_uuid_data:lower())
+
+    log.info("ble_file_fota", "UUID匹配结果:",
+        "服务匹配:", is_service_match,
+        "命令匹配:", is_cmd_match,
+        "数据匹配:", is_data_match)
+
+    if is_service_match then
+        if is_cmd_match then
+            -- 命令特征值:处理FOTA命令
+            log.info("ble_file_fota", "命令特征值匹配,处理命令")
+            handle_command(data)
+        elseif is_data_match then
+            -- 数据特征值:处理FOTA数据
+            log.info("ble_file_fota", "数据特征值匹配,处理数据")
+            handle_data(data)
+        else
+            log.warn("ble_file_fota", "未知的特征值UUID:", char_uuid)
+        end
+    else
+        log.warn("ble_file_fota", "未知的服务UUID:", service_uuid)
+    end
+end
+
+
+function ble_file_fota.proc_disconnect()
+    log.info("ble_file_fota", "处理连接断开事件")
+
+    -- 如果正在升级,连接断开则终止升级
+    if upgrade_state.is_upgrading then
+        log.error("ble_file_fota", "升级过程中连接断开,终止升级")
+        upgrade_state.is_upgrading = false
+
+        -- 删除临时文件
+        if upgrade_state.upgrade_file then
+            os.remove(upgrade_state.upgrade_file)
+        end
+
+        -- 结束FOTA流程
+        fota.finish(false)
+    end
+end
+
+
+
+return ble_file_fota

BIN
module/Air8101/demo/fota/ble_fota/ble_fota.bin


+ 246 - 0
module/Air8101/demo/fota/ble_fota/ble_fota_tool.py

@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+import asyncio
+import struct
+import time
+from bleak import BleakScanner, BleakClient
+
+# 完整UUID定义
+FOTA_SERVICE_UUID = "0000f000-0000-1000-8000-00805f9b34fb"  # 完整服务UUID
+FOTA_CMD_CHAR_UUID = "0000f001-0000-1000-8000-00805f9b34fb"  # 完整命令特征UUID
+FOTA_DATA_CHAR_UUID = "0000f002-0000-1000-8000-00805f9b34fb"  # 完整数据特征UUID
+
+# Command definitions
+CMD_START_UPGRADE = 0x01
+CMD_END_UPGRADE = 0x02
+
+# 每包数据大小
+MAX_PACKET_SIZE = 200
+
+class SimpleFotaTool:
+    def __init__(self, device_name, firmware_path):
+        self.device_name = device_name
+        self.firmware_path = firmware_path
+        self.client = None
+        self.firmware_data = None
+        self.total_size = 0
+        self.target_device = None
+
+    async def load_firmware(self):
+        """Load firmware file into memory"""
+        try:
+            with open(self.firmware_path, 'rb') as f:
+                self.firmware_data = f.read()
+            self.total_size = len(self.firmware_data)
+            print(f"   固件加载完成,大小: {self.total_size} 字节")
+            return True
+        except Exception as e:
+            print(f"   加载固件失败: {e}")
+            return False
+
+    async def scan_device(self):
+        """Scan for the target device"""
+        print("\n2. 扫描目标设备...")
+        print("   正在扫描,请等待...")
+
+        try:
+            devices = await BleakScanner.discover(timeout=10.0)
+
+            found_devices = []
+            for device in devices:
+                if device.name and self.device_name in device.name:
+                    found_devices.append(device)
+                    print(f"   找到匹配设备: {device.name} (地址: {device.address})")
+
+            if not found_devices:
+                print(f"   未找到设备: {self.device_name}")
+                return None
+
+            # 选择第一个匹配的设备
+            self.target_device = found_devices[0]
+            print(f"   选择设备: {self.target_device.name} (地址: {self.target_device.address})")
+            return self.target_device
+
+        except Exception as e:
+            print(f"   扫描失败: {e}")
+            return None
+
+    async def connect_device(self, device):
+        """Connect to the target device"""
+        print("\n3. 建立BLE连接...")
+        try:
+            self.client = BleakClient(device.address)
+            await self.client.connect(timeout=30.0)
+            print(f"   连接成功,状态: {self.client.is_connected}")
+
+            # 调试:打印所有服务和特征值
+            print("\n4. 发现服务和特征值...")
+
+            # 兼容不同版本的Bleak库
+            try:
+                # 新版本Bleak
+                services = self.client.services
+            except AttributeError:
+                # 旧版本Bleak
+                services = await self.client.get_services()
+
+            fota_service_found = False
+            for service in services:
+                if service.uuid.lower() == FOTA_SERVICE_UUID.lower():
+                    fota_service_found = True
+                    print(f"   找到FOTA服务: {service.uuid}")
+                    for char in service.characteristics:
+                        print(f"     特征值: {char.uuid} - 属性: {char.properties}")
+                        if char.uuid.lower() == FOTA_CMD_CHAR_UUID.lower():
+                            print(f"       -> 命令特征值 (可写)")
+                        elif char.uuid.lower() == FOTA_DATA_CHAR_UUID.lower():
+                            print(f"       -> 数据特征值 (可写)")
+
+            if not fota_service_found:
+                print("   警告: 未找到FOTA服务,但继续尝试...")
+
+            return True
+        except Exception as e:
+            print(f"   连接失败: {e}")
+            return False
+
+    async def write_characteristic(self, uuid, data):
+        """写入特征值"""
+        try:
+            await self.client.write_gatt_char(uuid, data, response=True)
+
+            # 正确提取短UUID(从完整UUID中提取f001/f002部分)
+            # 完整UUID格式: "0000f001-0000-1000-8000-00805f9b34fb"
+            # 我们想要提取 "f001" 部分
+            short_uuid = uuid.split('-')[0][-4:]
+            print(f"   写入特征值 {short_uuid},数据长度: {len(data)} 字节")
+            return True
+        except Exception as e:
+            print(f"   写入特征值失败: {e}")
+            return False
+
+    async def send_start_command(self):
+        """发送开始升级命令"""
+        print("\n5. 发送开始升级命令...")
+
+        # 连接成功后短暂延时
+        print("   连接成功,等待1秒...")
+        await asyncio.sleep(1)
+
+        # 发送开始升级命令
+        start_cmd = struct.pack("<BI", CMD_START_UPGRADE, self.total_size)
+        if not await self.write_characteristic(FOTA_CMD_CHAR_UUID, start_cmd):
+            return False
+
+        print("   开始命令发送完成")
+        await asyncio.sleep(1)  # 等待设备准备
+        return True
+
+    async def send_firmware_data(self):
+        """Send firmware data in chunks with optimized delay"""
+        print("\n6. 分块传输固件数据...")
+        sent_bytes = 0
+        start_time = time.time()
+        packet_count = 0
+
+        # 优化延时:减少到100ms以提高速度
+        PACKET_DELAY = 0.1
+
+        while sent_bytes < self.total_size:
+            chunk_size = min(MAX_PACKET_SIZE, self.total_size - sent_bytes)
+            chunk = self.firmware_data[sent_bytes:sent_bytes + chunk_size]
+
+            if not await self.write_characteristic(FOTA_DATA_CHAR_UUID, chunk):
+                return False
+
+            sent_bytes += chunk_size
+            packet_count += 1
+
+            # 短暂延时,避免数据丢失
+            await asyncio.sleep(PACKET_DELAY)
+
+            # 每20个数据包显示一次进度
+            if packet_count % 20 == 0 or sent_bytes >= self.total_size:
+                progress = (sent_bytes / self.total_size) * 100
+                elapsed = time.time() - start_time
+                speed = sent_bytes / elapsed / 1024 if elapsed > 0 else 0
+                remaining_time = (self.total_size - sent_bytes) / (sent_bytes / elapsed) if sent_bytes > 0 else 0
+                print(f"   进度: {progress:.1f}% - {speed:.1f} KB/s - 已发送 {packet_count} 包 - 预计剩余: {remaining_time:.1f}s")
+
+        total_time = time.time() - start_time
+        avg_speed = self.total_size / total_time / 1024
+        print(f"   数据传输完成! 总时间: {total_time:.1f}s, 平均速度: {avg_speed:.1f} KB/s")
+        return True
+
+    async def end_upgrade(self):
+        """Send end upgrade command"""
+        print("\n7. 发送结束升级命令...")
+        end_cmd = struct.pack("B", CMD_END_UPGRADE)
+        if not await self.write_characteristic(FOTA_CMD_CHAR_UUID, end_cmd):
+            return False
+
+        print("   结束命令发送完成")
+
+        # 等待设备处理
+        print("\n8. 等待设备处理升级...")
+        await asyncio.sleep(5)  # 给设备足够时间处理
+        return True
+
+    async def run(self):
+        """Main execution flow"""
+
+        # 1. 加载固件文件
+        print("\n1. 加载固件文件...")
+        if not await self.load_firmware():
+            return False
+
+        # 2. 扫描目标设备
+        device = await self.scan_device()
+        if not device:
+            return False
+
+        # 3. 连接设备
+        if not await self.connect_device(device):
+            return False
+
+        try:
+            # 4. 发送开始命令
+            if not await self.send_start_command():
+                return False
+
+            # 5. 发送固件数据
+            if not await self.send_firmware_data():
+                return False
+
+            # 6. 结束升级
+            if not await self.end_upgrade():
+                return False
+
+            print("\n" + "="*50)
+            print("升级流程完成! 设备应该正在重启...")
+            print("="*50)
+            return True
+
+        except Exception as e:
+            print(f"   升级过程中出现错误: {e}")
+            return False
+        finally:
+            # 断开连接
+            if self.client and self.client.is_connected:
+                await self.client.disconnect()
+                print("   已断开连接")
+
+async def main():
+    import argparse
+    parser = argparse.ArgumentParser(description="蓝牙FOTA升级工具")
+    parser.add_argument("-f", "--firmware", required=True, help="固件文件路径")
+    parser.add_argument("-d", "--device", default="Air8101_FOTA", help="设备名称")
+
+    args = parser.parse_args()
+
+    tool = SimpleFotaTool(args.device, args.firmware)
+    success = await tool.run()
+    return success
+
+if __name__ == "__main__":
+    success = asyncio.run(main())
+    exit(0 if success else 1)

+ 236 - 0
module/Air8101/demo/fota/ble_fota/ble_main.lua

@@ -0,0 +1,236 @@
+--[[
+@module  ble_main
+@summary BLE服务主功能模块
+@version 1.0
+@date    2025.12.08
+@author  孟伟
+@usage
+-- BLE服务主功能模块
+-- 提供BLE服务的初始化、配置和事件处理
+-- 不包含FOTA业务逻辑,仅处理BLE相关功能
+
+依赖模块:
+- ble_file_fota: 用于处理FOTA相关业务逻辑(文件写入方式)
+- ble_packet_fota: 用于处理FOTA相关业务逻辑(分段写入方式)
+]]
+
+-- 选择FOTA升级方式:"file" 或 "packet"
+-- 1. file方式:将升级包数据先写入本地文件,然后调用fota.file()进行升级
+-- 2. packet方式:直接使用fota.packet()处理分段数据,不写入文件,适合差分升级
+local fota_mode = "packet" -- 默认使用packet方式
+
+-- 根据选择加载对应的FOTA模块
+local ble_fota_main
+if fota_mode == "file" then
+    ble_fota_main = require "ble_file_fota"
+else
+    ble_fota_main = require "ble_packet_fota"
+end
+
+-- ble_main的任务名
+local TASK_NAME = "BLE_MAIN"
+
+-- 配置参数
+config = {
+    device_name = "Air8101_FOTA", -- 设备广播名称
+    service_uuid = "F000",        -- FOTA服务UUID(短格式)
+    char_uuid_cmd = "F001",       -- 命令特征值UUID
+    char_uuid_data = "F002",      -- 数据特征值UUID
+    max_packet_size = 20          -- BLE数据包最大长度(字节)
+}
+
+local bluetooth_device = nil
+local ble_device = nil
+local adv_create = nil
+local gatt_create = nil
+
+-- GATT服务数据库定义
+-- 这里定义了BLE设备提供的服务和特征值
+local att_db = {
+    string.fromHex(config.service_uuid), -- Service UUID
+    {
+        string.fromHex(config.char_uuid_cmd),
+        ble.WRITE | ble.WRITE_CMD
+    },
+    {
+        string.fromHex(config.char_uuid_data),
+        ble.WRITE | ble.WRITE_CMD
+    }
+}
+
+-- BLE事件回调函数
+local function ble_event_cb(ble_dev, event, param)
+    log.info("BLE_EVENT", "收到BLE事件:", event)
+
+    -- 根据LuatOS BLE事件枚举处理不同事件
+    if event == ble.EVENT_CONN then
+        -- 连接成功事件
+        log.info("BLE_EVENT", "设备已连接", "地址:", param.addr and param.addr:toHex() or "未知")
+        sys.sendMsg(TASK_NAME, "BLE_EVENT", "CONNECT", param)
+    elseif event == ble.EVENT_DISCONN then
+        -- 连接断开事件
+        log.info("BLE_EVENT", "设备已断开连接", "原因:", param.reason or "未知")
+        sys.sendMsg(TASK_NAME, "BLE_EVENT", "DISCONNECTED", param.reason)
+    elseif event == ble.EVENT_WRITE then
+        -- 写入事件 - 这是关键事件!
+        log.info("BLE_EVENT", "处理写入事件")
+
+        -- 检查参数是否完整
+        if not param or not param.uuid_service or not param.uuid_characteristic or not param.data then
+            log.error("BLE_EVENT", "写入事件参数不完整")
+            return
+        end
+
+        -- 获取服务UUID和特征值UUID
+        local service_uuid = param.uuid_service:toHex()
+        local char_uuid = param.uuid_characteristic:toHex()
+        local data = param.data
+
+        log.info("BLE_EVENT", "服务UUID:", service_uuid)
+        log.info("BLE_EVENT", "特征值UUID:", char_uuid)
+        log.info("BLE_EVENT", "数据长度:", #data, "字节")
+        sys.sendMsg(TASK_NAME, "BLE_EVENT", "WRITE_REQ", param)
+    elseif event == ble.EVENT_READ then
+        -- 读取事件 - 外围设备收到主设备读请求
+        log.info("BLE_EVENT", "处理读取事件")
+    elseif event == ble.EVENT_READ_VALUE then
+        -- 读取操作完成事件 - 中心设备读取特征值完成
+        log.info("BLE_EVENT", "读取操作完成", "数据:", param.data and param.data:toHex() or "无数据")
+    elseif event == ble.EVENT_SCAN_REPORT then
+        -- 扫描报告事件 - 中心设备扫描到其他BLE设备
+        log.info("BLE_EVENT", "扫描报告", "RSSI:", param.rssi, "地址:", param.adv_addr and param.adv_addr:toHex() or "未知")
+    elseif event == ble.EVENT_SCAN_STOP then
+        -- 扫描停止事件
+        log.info("BLE_EVENT", "扫描停止")
+    else
+        -- 其他事件
+        log.info("BLE_EVENT", "其他事件类型:", event)
+        if param then
+            -- 尝试打印参数的基本信息,避免直接打印table导致错误
+            if type(param) == "table" then
+                log.info("BLE_EVENT", "事件参数为table,包含字段:", #param)
+                for k, v in pairs(param) do
+                    if type(v) == "string" then
+                        log.info("BLE_EVENT", "参数字段:", k, "值:", v:toHex())
+                    else
+                        log.info("BLE_EVENT", "参数字段:", k, "类型:", type(v))
+                    end
+                end
+            else
+                log.info("BLE_EVENT", "事件参数类型:", type(param))
+            end
+        end
+    end
+end
+
+-- 初始化BLE功能
+local function ble_init()
+    log.info("BLE_INIT", "开始初始化BLE...")
+
+    -- 初始化蓝牙核心
+    bluetooth_device = bluetooth_device or bluetooth.init()
+    if not bluetooth_device then
+        log.error("BLE_INIT", "蓝牙核心初始化失败")
+        return false
+    end
+    log.info("BLE_INIT", "蓝牙核心初始化成功")
+
+    -- 初始化BLE功能
+    ble_device = ble_device or bluetooth_device:ble(ble_event_cb)
+    if not ble_device then
+        log.error("BLE_INIT", "BLE功能初始化失败")
+        return false
+    end
+    log.info("BLE_INIT", "BLE功能初始化成功")
+
+    -- 创建GATT服务
+    gatt_create = gatt_create or ble_device:gatt_create(att_db)
+    if not gatt_create then
+        log.error("BLE_INIT", "GATT服务创建失败")
+        return false
+    end
+    log.info("BLE_INIT", "GATT服务创建成功")
+
+    -- 配置广播数据
+    log.info("BLE_INIT", "配置广播数据...")
+    adv_create = adv_create or ble_device:adv_create({
+        addr_mode = ble.PUBLIC,
+        channel_map = ble.CHNLS_ALL,
+        intv_min = 120,
+        intv_max = 120,
+        adv_data = {
+            { ble.FLAGS,               string.char(0x06) },  -- BLE标志
+            { ble.COMPLETE_LOCAL_NAME, config.device_name }, -- 设备名称
+        }
+    })
+
+    if not adv_create then
+        log.error("BLE_INIT", "广播配置失败")
+        return false
+    end
+    log.info("BLE_INIT", "广播配置成功")
+
+    -- 开始广播
+    ble_device:adv_start()
+    log.info("BLE_INIT", "BLE广播已启动,设备名称:", config.device_name)
+
+    return true
+end
+
+-- 主任务处理函数
+local function ble_main_task_func()
+    local result, msg
+
+    while true do
+        result = ble_init()
+        if not result then
+            log.error("ble_main_task_func", "ble_init error")
+            goto EXCEPTION_PROC
+        end
+
+        while true do
+            msg = sys.waitMsg(TASK_NAME, "BLE_EVENT")
+
+            if not msg then
+                log.error("ble_main_task_func", "waitMsg timeout")
+                goto EXCEPTION_PROC
+            end
+
+            if msg[2] == "CONNECT" then
+                local conn_param = msg[3]
+                log.info("BLE", "设备连接成功: " .. conn_param.addr:toHex())
+            elseif msg[2] == "DISCONNECTED" then
+                log.info("BLE", "设备断开连接,原因: " .. msg[3])
+                -- 通知FOTA模块连接断开
+                ble_fota_main.proc_disconnect()
+                break
+            -- 收到中心设备的写请求,将写的数据发给ble_fota_main模块处理
+            elseif msg[2] == "WRITE_REQ" then
+                local ble_param = msg[3]
+                local service_uuid = ble_param.uuid_service:toHex()
+                local char_uuid = ble_param.uuid_characteristic:toHex()
+                local data = ble_param.data
+
+                log.info("BLE", "收到写请求: " .. service_uuid .. " " .. char_uuid .. " " .. data:toHex())
+                ble_fota_main.proc(service_uuid, char_uuid, data)
+            end
+        end
+
+        ::EXCEPTION_PROC::
+        log.error("ble_main_task_func", "异常退出, 5秒后重新开启广播")
+
+        -- 停止广播
+        if ble_device then
+            ble_device:adv_stop()
+        end
+
+        -- 清空此task绑定的消息队列中的未处理的消息
+        sys.cleanMsg(TASK_NAME)
+
+        -- 5秒后跳转到循环体开始位置,自动重试
+        sys.wait(5000)
+    end
+end
+
+-- 启动主任务
+sys.taskInitEx(ble_main_task_func, TASK_NAME)

+ 251 - 0
module/Air8101/demo/fota/ble_fota/ble_packet_fota.lua

@@ -0,0 +1,251 @@
+--[[
+@module  ble_packet_fota
+@summary 蓝牙FOTA升级功能模块(分段写入方式)
+@version 1.0
+@date    2025.12.08
+@author  孟伟
+@usage
+-- 蓝牙FOTA升级功能(分段写入方式)
+-- 提供通过蓝牙低功耗(BLE)接收升级包数据进行固件升级的功能
+
+本文件为FOTA业务逻辑处理模块,核心业务逻辑为:
+1. 处理接收到的BLE写入请求数据
+2. 实现FOTA升级流程的控制(分段写入方式)
+3. 管理升级状态和分段数据操作
+
+本文件的对外接口有1个:
+1. ble_packet_fota.proc(service_uuid, char_uuid, data): 处理接收到的BLE写入请求数据
+
+依赖模块:
+- ble_main: 用于提供BLE服务和事件处理
+]]
+
+local ble_packet_fota = {}
+
+-- 升级状态管理
+local upgrade_state = {
+    is_upgrading = false, -- 是否正在升级
+    total_size = 0,       -- 总文件大小(字节)
+    received_size = 0,    -- 已接收大小(字节)
+    upgrade_packet = 0    -- 升级包计数器
+}
+
+-- 配置参数
+local config = {
+    service_uuid = "F000",   -- FOTA服务UUID(短格式)
+    char_uuid_cmd = "F001",  -- 命令特征值UUID
+    char_uuid_data = "F002", -- 数据特征值UUID
+    max_packet_size = 200    -- BLE数据包最大长度(字节)
+}
+local function ble_reboot()
+    -- 完成FOTA流程并重启
+    fota.finish(true)
+    log.info("FOTA_CMD", "正在重启设备...")
+    rtos.reboot()
+end
+-- 处理FOTA命令
+-- @param cmd_data 命令数据,格式:[命令码(1字节)] 或 [命令码(1字节) + 文件大小(4字节)]
+local function handle_command(cmd_data)
+    log.info("FOTA_CMD", "收到命令数据:", cmd_data:toHex(), "长度:", #cmd_data)
+
+    -- 检查命令数据是否有效
+    if #cmd_data < 1 then
+        log.error("FOTA_CMD", "命令数据为空")
+        return
+    end
+
+    -- 解析命令码(第一个字节)
+    local cmd = cmd_data:byte(1)
+    log.info("FOTA_CMD", "解析命令码:", cmd, string.format("(0x%02X)", cmd))
+
+    -- 命令0x01:开始升级
+    if cmd == 0x01 then
+        log.info("FOTA_CMD", "处理开始升级命令")
+
+        -- 检查命令格式:需要至少5字节(1字节命令码 + 4字节文件大小)
+        if #cmd_data >= 5 then
+            -- 解析文件大小(小端序,从第2字节开始)
+            local total_size = string.unpack("<I4", cmd_data, 2)
+            log.info("FOTA_CMD", "文件总大小:", total_size, "字节")
+
+            -- 初始化FOTA子系统
+            log.info("FOTA_CMD", "初始化FOTA子系统...")
+            if fota.init() then
+                log.info("FOTA_CMD", "FOTA初始化成功")
+
+                -- FOTA底层已准备就绪,无需等待
+                log.info("FOTA_CMD", "FOTA底层准备就绪")
+
+                -- 更新升级状态
+                upgrade_state.is_upgrading = true
+                upgrade_state.total_size = total_size
+                upgrade_state.received_size = 0
+                upgrade_state.upgrade_packet = 0
+
+                log.info("FOTA_CMD", "升级状态已设置",
+                    "总大小:", upgrade_state.total_size)
+                log.info("FOTA_CMD", "准备接收固件数据...")
+            else
+                log.error("FOTA_CMD", "FOTA初始化失败")
+            end
+        else
+            log.error("FOTA_CMD", "开始命令格式错误,长度不足")
+        end
+
+        -- 命令0x02:结束升级(通知升级包发完)
+    elseif cmd == 0x02 then
+        log.info("FOTA_CMD", "处理结束升级命令")
+
+        -- 检查是否处于升级状态
+        if not upgrade_state.is_upgrading then
+            log.warn("FOTA_CMD", "未处于升级状态,忽略结束命令")
+            return
+        end
+
+        -- 验证文件完整性
+        log.info("FOTA_CMD", "验证文件完整性...")
+        log.info("FOTA_CMD", "已接收:", upgrade_state.received_size, "字节")
+        log.info("FOTA_CMD", "应接收:", upgrade_state.total_size, "字节")
+
+        if upgrade_state.received_size == upgrade_state.total_size then
+            log.info("FOTA_CMD", "文件完整性验证通过")
+            log.info("FOTA_CMD", "升级数据已全部接收,等待升级完成...")
+
+            -- 等待底层校验结束
+            local success = false
+            for i = 1, 30 do -- 最多等待3秒
+                sys.wait(100)
+                local succ, fotaDone = fota.isDone()
+                if not succ then
+                    log.error("FOTA_CMD", "校验过程出错")
+                    fota.finish(false)
+                    upgrade_state.is_upgrading = false
+                    break
+                end
+                if fotaDone then
+                    log.info("FOTA_CMD", "FOTA升级成功!")
+
+                    -- 延迟重启,给用户一些反应时间
+                    log.info("FOTA_CMD", "2秒后设备将自动重启...,重启后通过日志判断最终是否升级成功")
+
+                    -- 延迟2秒后重启设备
+                    sys.timerStart(ble_reboot, 2000)
+                    success = true
+                    break
+                end
+            end
+
+            if not success then
+                log.error("FOTA_CMD", "校验超时")
+                fota.finish(false)
+                upgrade_state.is_upgrading = false
+            end
+        else
+            log.error("FOTA_CMD", "文件不完整,升级失败")
+            -- 清理升级状态
+            upgrade_state.is_upgrading = false
+            fota.finish(false)
+        end
+
+        log.info("FOTA_CMD", "结束升级命令处理完成")
+    else
+        log.warn("FOTA_CMD", "未知命令码:", cmd, string.format("(0x%02X)", cmd))
+    end
+end
+
+-- 处理FOTA数据
+-- @param data 固件数据块
+local function handle_data(data)
+    log.info("FOTA_DATA", "收到数据包,长度:", #data, "字节")
+
+    -- 检查是否处于升级状态
+    if not upgrade_state.is_upgrading then
+        log.warn("FOTA_DATA", "未处于升级状态,忽略数据")
+        return
+    end
+
+    -- 直接使用fota.run()处理分段数据,不写入文件
+    log.info("FOTA_DATA", "处理分段数据,包序号:", upgrade_state.upgrade_packet)
+    local result, isDone = fota.run(data)
+    log.info("FOTA_DATA", "分段写入结果:", "result:", result, "isDone:", isDone)
+
+    if result then
+        -- 更新接收状态
+        upgrade_state.received_size = upgrade_state.received_size + #data
+        upgrade_state.upgrade_packet = upgrade_state.upgrade_packet + 1
+
+        -- 计算并显示进度
+        local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
+
+        -- 每50个数据包或完成时打印进度
+        if upgrade_state.received_size % (config.max_packet_size * 50) == 0 or
+            upgrade_state.received_size >= upgrade_state.total_size then
+            log.info("FOTA_DATA", "升级进度:", progress, "%",
+                "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
+        end
+
+        log.info("FOTA_DATA", "数据写入成功,当前总计:", upgrade_state.received_size, "字节")
+
+        -- 如果所有数据都已接收,检查升级是否完成
+        if upgrade_state.received_size >= upgrade_state.total_size then
+            log.info("FOTA_DATA", "所有数据已接收,等待升级完成...")
+        end
+    else
+        log.error("FOTA_DATA", "分段写入失败")
+
+        -- 分段写入失败,终止升级
+        upgrade_state.is_upgrading = false
+        fota.finish(false)
+    end
+end
+
+-- 处理接收到的BLE写入请求数据
+-- @param service_uuid 服务UUID
+-- @param char_uuid 特征值UUID
+-- @param data 写入的数据
+function ble_packet_fota.proc(service_uuid, char_uuid, data)
+    log.info("ble_packet_fota", "处理写入数据", service_uuid, char_uuid, data:toHex())
+
+    -- 简化的UUID匹配逻辑:检查UUID是否包含我们的短UUID
+    local is_service_match = string.find(service_uuid:lower(), config.service_uuid:lower())
+    local is_cmd_match = string.find(char_uuid:lower(), config.char_uuid_cmd:lower())
+    local is_data_match = string.find(char_uuid:lower(), config.char_uuid_data:lower())
+
+    log.info("ble_packet_fota", "UUID匹配结果:",
+        "服务匹配:", is_service_match,
+        "命令匹配:", is_cmd_match,
+        "数据匹配:", is_data_match)
+
+    if is_service_match then
+        if is_cmd_match then
+            -- 命令特征值:处理FOTA命令
+            log.info("ble_packet_fota", "命令特征值匹配,处理命令")
+            handle_command(data)
+        elseif is_data_match then
+            -- 数据特征值:处理FOTA数据
+            log.info("ble_packet_fota", "数据特征值匹配,处理数据")
+            handle_data(data)
+        else
+            log.warn("ble_packet_fota", "未知的特征值UUID:", char_uuid)
+        end
+    else
+        log.warn("ble_packet_fota", "未知的服务UUID:", service_uuid)
+    end
+end
+
+-- 处理BLE连接断开事件
+-- @return nil
+function ble_packet_fota.proc_disconnect()
+    log.info("ble_packet_fota", "处理连接断开事件")
+
+    -- 如果正在升级,连接断开则终止升级
+    if upgrade_state.is_upgrading then
+        log.error("ble_packet_fota", "升级过程中连接断开,终止升级")
+        upgrade_state.is_upgrading = false
+
+        -- 结束FOTA流程
+        fota.finish(false)
+    end
+end
+
+return ble_packet_fota

+ 92 - 0
module/Air8101/demo/fota/ble_fota/main.lua

@@ -0,0 +1,92 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+Air8101模块的蓝牙FOTA升级功能演示;
+
+通过蓝牙低功耗(BLE)方式实现固件升级的应用场景:
+
+1、蓝牙分段升级:通过BLE连接将升级包文件分多个片段发送,每个片段接收并写入,代码演示通过蓝牙特征值传输固件数据进行升级;
+
+2、无需有线连接:利用BLE无线通信特性,实现设备固件的无线远程升级;
+
+适用场景:
+    - 蓝牙设备固件升级 -> 智能硬件、可穿戴设备等蓝牙连接设备
+    - 自定义升级流程 -> 通过BLE特征值实现命令控制和数据传输
+
+更多说明参考本目录下的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 = "001.000.000" --不同于使用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(3000)
+        log.info("fota", "version", VERSION)
+        -- log.info("hezhou ble_fota test")
+    end
+end
+sys.taskInit(print_version)
+
+-- 加载蓝牙FOTA功能模块
+require "ble_main"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 717 - 0
module/Air8101/demo/fota/ble_fota/readme.md

@@ -0,0 +1,717 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口,负责初始化系统和启动蓝牙FOTA服务;
+
+2、ble_main.lua:BLE服务主功能模块,负责蓝牙连接管理、事件处理和数据传输;支持配置选择FOTA升级方式;
+
+3、ble_file_fota.lua:FOTA业务逻辑模块(文件写入方式),负责升级流程控制、命令处理和文件操作;
+
+4、ble_packet_fota.lua:FOTA业务逻辑模块(分段写入方式),负责升级流程控制、命令处理和分段数据操作;
+
+5、ble_fota_tool.py:Python脚本工具,用于通过蓝牙发送升级包,演示ble升级的完整流程;
+
+6、ble_fota.bin:演示蓝牙FOTA升级的升级包文件,用于测试升级功能的完整性;内容只升级了版本号,加了个打印
+
+## 演示功能概述
+
+本demo演示的核心功能为:
+
+Air8101模块通过BLE(蓝牙低功耗)进行固件远程升级(FOTA)的完整实现方案,支持两种升级方式:
+
+1. **文件写入方式**:将接收到的升级包数据先保存到本地临时文件,完成数据接收后再执行升级
+   - 优点:适合完整固件升级,逻辑清晰,易于调试
+   - 缺点:需要额外的文件系统分区存储空间保存完整固件
+   - 适用场景:存储空间充足的设备
+
+2. **分段写入方式**:直接将接收到的升级包数据通过fota.run()函数处理,无需保存到本地文件
+   - 优点:节省存储空间,适合差分升级
+   - 缺点:对内存要求较高,需要足够的内存缓冲区实时处理数据
+   - 适用场景:存储空间有限的设备
+
+适用场景:
+- 智能硬件、可穿戴设备等蓝牙连接设备的固件升级
+- 需要自定义升级流程的应用场景
+- 无网络环境下的设备固件更新
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air8101/luatos/common/hwenv/image/Air8101.png)
+
+1、Air8101 核心板一块
+
+2、TYPE-C USB数据线一根 ,Air8101 核心板和数据线的硬件接线方式为:
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具,需要注意的是luatools工具版本必须为3.1.10及以上版本,否则制作的升级包没办法升级。
+
+2、[Air8101 V2001版本固件以上,25/12/17日后的固件)](https://docs.openluat.com/air8101/luatos/firmware/)
+
+3、Python 3 环境(用于运行ble_fota_tool.py发送升级包)
+      注意需要安装bleak库,可通过pip安装:
+
+      ```bash
+         pip install bleak
+      ```
+
+## 蓝牙FOTA升级工具使用说明
+
+### 工具功能
+- 连接Air8101设备并发送升级包
+- 显示升级进度和状态信息
+- 支持自定义升级包文件名
+
+### 使用方法
+1. 确保Air8101设备处于蓝牙广播状态,设备名称为"Air8101_FOTA"
+2. 在命令行中运行ble_fota_tool.py脚本,指定升级包文件名
+   ```bash
+   python ble_fota_tool.py -f 升级包文件名
+   ```
+
+### 参数说明
+
+- -d 或 --device - 指定目标设备名称(默认值为"Air8101_FOTA")
+
+- -f 或 --firmware - 指定固件文件路径(必需参数)
+
+### 工作流程
+当运行脚本后,它会自动执行以下步骤:
+
+1. 加载指定的固件文件
+2. 扫描并发现蓝牙设备
+3. 连接到目标设备
+4. 发送开始升级命令
+5. 发送固件数据到设备
+6. 发送结束升级命令
+7. 等待设备处理升级
+8. 断开连接并完成升级过程
+
+脚本会在每个步骤显示详细的日志信息,包括连接状态、数据传输进度等,方便用户监控整个升级过程。
+
+## 蓝牙GATT服务设计
+
+### 服务与特征值定义
+| 类型 |UUID | 属性 | 功能描述 |
+|------|------------|------|----------|
+| Service | F000 | - | FOTA服务主UUID,用于标识升级服务 |
+| Characteristic | F001 | Write Without Response | 命令特征值,用于发送升级控制命令 |
+| Characteristic | F002 | Write Without Response | 数据特征值,用于发送升级包数据 |
+
+### 命令报文格式
+
+#### 开始升级命令
+- **命令码**:0x01
+- **格式**:`[命令码(1字节)] + [文件大小(4字节)]`
+- **示例**:`01 75 15 00 00` 表示开始升级,固件大小为5493字节
+- **说明**:
+  - 命令码固定为0x01
+  - 文件大小为4字节小端序
+  - 设备收到此命令后,初始化FOTA子系统并准备接收数据
+
+#### 结束升级命令
+- **命令码**:0x02
+- **格式**:`[命令码(1字节)]`
+- **示例**:`02`
+- **说明**:
+  - 命令码固定为0x02
+  - 设备收到此命令后,验证固件完整性并执行升级
+
+### 数据报文格式
+
+- **格式**:`[固件数据(n字节)]`
+- **默认长度**:200字节(可通过ble_fota_tool.py配置调整,最大256)
+- **说明**:
+  - 固件数据为二进制格式
+  - 每包数据大小不超过BLE数据包最大长度:Air8101系列模组硬件支持最大256字节,考虑到稳定性建议保持默认的200字节
+  - 设备收到数据后,根据所选的FOTA方式进行处理:
+    - 文件方式:将数据写入临时文件
+    - 分段方式:直接通过fota.run()处理数据
+
+### 升级流程
+
+#### 用法:
+1. 先把脚本和固件烧录到Air8101模块中,并确认设备正常启动
+2. 模块启动后会自动开启BLE广播,广播名称为"Air8101_FOTA"
+3. 在电脑端操作:运行ble_fota_tool.py脚本连接设备并发送升级固件
+    注意:确保升级文件名为正确格式,并且与ble_fota_tool.py在同一目录下
+4. 观察日志输出确认升级进度
+5. 模块接收并验证固件成功后,会自动重启并应用新固件
+
+#### BLE通讯过程说明
+蓝牙FOTA升级通过BLE特征值进行命令控制和数据传输:
+协议流程:
+1. 上位机通过BLE扫描并连接名为"Air8101_FOTA"的设备
+2. 上位机向命令特征值(F001)发送开始升级命令(0x01)和固件大小
+3. 设备初始化FOTA功能并准备接收数据
+4. 上位机向数据特征值(F002)分包发送固件数据
+5. 设备接收并保存数据到临时文件
+6. 上位机发送结束升级命令(0x02)
+7. 设备验证固件完整性并执行FOTA升级流程
+8. 升级成功后设备自动重启
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、根据ble_main.lua中的fota_mode选择FOTA升级方式:"file" 或 "packet",默认是"packet"方式。
+   ```lua
+   -- 选择FOTA升级方式,"file" 或 "packet"
+   local fota_mode = "packet"
+   ```
+
+3、测试的时候可以直接使用demo目录下已经制作好的升级包,也可以自己制作升级包。
+
+   自己制作升级包时使用Luatools制作升级包,流程参考[升级包制作流程](https://docs.openluat.com/osapi/core/fota/#51)。将制作好的升级包放在ble_fota_tool.py同级目录下
+
+4、使用Luatools烧录内核固件和demo脚本代码,烧录成功后,自动开机运行。
+
+5、确认设备处于蓝牙广播状态(设备名称为"Air8101_FOTA")
+
+6、运行Python脚本发送升级包:
+
+```bash
+python ble_fota_tool.py -f ble_fota.bin
+```
+
+7、脚本会自动扫描并连接到目标设备,发送升级命令并传输固件文件
+
+8、设备接收并验证升级包,校验升级包没问题后会启动升级并重启,重启后项目版本号会从1.0.0更新为1.0.1
+
+9、可以看到如下日志:
+
+   蓝牙FOTA升级日志解读:
+
+   1. 设备启动,蓝牙服务初始化成功
+
+   2. 收到开始升级命令,初始化FOTA分区和缓冲区
+
+   3. 开始分段接收固件数据
+
+   4. 所有数据接收完成,验证固件完整性
+
+   5. 升级完成,设备重启
+
+   6. 重启后新版本正常运行,确认升级成功
+
+
+
+packet 升级方式日志如下:
+```lua
+[2025-12-17 16:54:14.565] luat:U(1248):I/user.BLE_INIT 开始初始化BLE...
+[2025-12-17 16:54:14.565] luat:D(1249):bluetooth:执行bt初始化
+[2025-12-17 16:54:14.565] bt_ipc:W(1248):bt_ipc_init bt ipc already initialised
+[2025-12-17 16:54:14.565] rwnx_cal_set_rfconfig(0x101) phy on; rf off
+[2025-12-17 16:54:14.658] luat:U(1326):I/user.BLE_INIT 蓝牙核心初始化成功
+[2025-12-17 16:54:14.658] luat:D(1326):bluetooth:cmd:27
+[2025-12-17 16:54:14.658] luat:U(1327):I/user.BLE_INIT BLE功能初始化成功
+[2025-12-17 16:54:14.658] luat:U(1327):I/user.BLE_INIT GATT服务创建成功
+[2025-12-17 16:54:14.658] luat:U(1328):I/user.BLE_INIT 配置广播数据...
+[2025-12-17 16:54:14.658] luat:D(1334):bluetooth:ble_cmd_cb cmd:1 status:0
+[2025-12-17 16:54:14.690] luat:D(1340):bluetooth:ble_cmd_cb cmd:2 status:0
+[2025-12-17 16:54:14.690] luat:U(1341):I/user.BLE_INIT 广播配置成功
+[2025-12-17 16:54:14.690] luat:D(1347):bluetooth:ble_cmd_cb cmd:4 status:0
+[2025-12-17 16:54:14.690] luat:U(1348):I/user.BLE_INIT BLE广播已启动,设备名称: Air8101_FOTA
+[2025-12-17 16:54:14.690] luat:U(1350):I/user.BLE_EVENT 收到BLE事件: 1
+[2025-12-17 16:54:14.690] luat:U(1351):I/user.BLE_EVENT 其他事件类型: 1
+[2025-12-17 16:54:14.690] luat:U(1351):I/user.BLE_EVENT 收到BLE事件: 3
+[2025-12-17 16:54:14.690] luat:U(1351):I/user.BLE_EVENT 其他事件类型: 3
+[2025-12-17 16:54:14.690] luat:U(1352):I/user.BLE_EVENT 收到BLE事件: 4
+[2025-12-17 16:54:14.690] luat:U(1352):I/user.BLE_EVENT 其他事件类型: 4
+[2025-12-17 16:54:17.448] luat:U(4110):I/user.fota version 001.000.000
+[2025-12-17 16:54:20.440] luat:U(7110):I/user.fota version 001.000.000
+[2025-12-17 16:54:23.438] luat:U(10110):I/user.fota version 001.000.000
+[2025-12-17 16:54:26.434] luat:U(13110):I/user.fota version 001.000.000
+[2025-12-17 16:54:29.440] luat:U(16110):I/user.fota version 001.000.000
+[2025-12-17 16:54:32.434] luat:U(19110):I/user.fota version 001.000.000
+[2025-12-17 16:54:32.870] ap0:ble:W(19432):appm_msg_handler,src_task_id:0x11,dest_id:0x0,src_id:0x0,msgid:0x1104
+[2025-12-17 16:54:32.870] ap0:ble:W(19432):[gapc_connection_req_ind_handler]conidx:0,dest_id:0x0,con_peer_addr:92:6d:c7:33:13:14
+[2025-12-17 16:54:32.870] luat:U(19546):I/user.BLE_EVENT 收到BLE事件: 12
+[2025-12-17 16:54:32.870] luat:U(19546):I/user.BLE_EVENT 设备已连接 地址: 141333C76D92
+[2025-12-17 16:54:32.886] luat:U(19547):I/user.BLE 设备连接成功: 141333C76D92
+[2025-12-17 16:54:33.975] luat:U(20639):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:33.975] luat:U(20639):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:33.975] luat:U(20640):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:33.975] luat:U(20640):I/user.BLE_EVENT 特征值UUID: F001
+[2025-12-17 16:54:33.975] luat:U(20640):I/user.BLE_EVENT 数据长度: 5 字节
+[2025-12-17 16:54:33.975] luat:U(20641):I/user.BLE 收到写请求: F000 F001 01C8A90000
+[2025-12-17 16:54:33.975] luat:U(20641):I/user.ble_packet_fota 处理写入数据 F000 F001 01C8A90000 10
+[2025-12-17 16:54:33.975] luat:U(20641):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: 1 数据匹配: nil
+[2025-12-17 16:54:33.975] luat:U(20642):I/user.ble_packet_fota 命令特征值匹配,处理命令
+[2025-12-17 16:54:33.975] luat:U(20642):I/user.FOTA_CMD 收到命令数据: 01C8A90000 长度: 5
+[2025-12-17 16:54:33.975] luat:U(20642):I/user.FOTA_CMD 解析命令码: 1 (0x01)
+[2025-12-17 16:54:33.975] luat:U(20642):I/user.FOTA_CMD 处理开始升级命令
+[2025-12-17 16:54:33.975] luat:U(20643):I/user.FOTA_CMD 文件总大小: 43464 字节
+[2025-12-17 16:54:33.975] luat:U(20643):I/user.FOTA_CMD 初始化FOTA子系统...
+[2025-12-17 16:54:33.975] luat:U(20643):I/user.FOTA_CMD FOTA初始化成功
+[2025-12-17 16:54:33.975] luat:U(20644):I/user.FOTA_CMD FOTA底层准备就绪
+[2025-12-17 16:54:33.975] luat:U(20644):I/user.FOTA_CMD 升级状态已设置 总大小: 43464
+[2025-12-17 16:54:33.975] luat:U(20644):I/user.FOTA_CMD 准备接收固件数据...
+[2025-12-17 16:54:35.063] luat:U(21730):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:35.063] luat:U(21730):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:35.063] luat:U(21730):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:35.063] luat:U(21731):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:54:35.063] luat:U(21731):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:54:35.063] luat:U(21731):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 16:54:35.063] luat:U(21732):I/user.ble_packet_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 16:54:35.063] luat:U(21733):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:54:35.063] luat:U(21733):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:54:35.063] luat:U(21733):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:54:35.063] luat:U(21733):I/user.FOTA_DATA 处理分段数据,包序号: 0
+[2025-12-17 16:54:35.063] luat:U(21733):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:54:35.063] luat:U(21734):I/user.FOTA_DATA 数据写入成功,当前总计: 200 字节
+[2025-12-17 16:54:35.204] luat:U(21867):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:35.204] luat:U(21868):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:35.204] luat:U(21868):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:35.204] luat:U(21868):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:54:35.204] luat:U(21868):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:54:35.204] luat:U(21869):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 16:54:35.204] luat:U(21870):I/user.ble_packet_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 16:54:35.204] luat:U(21870):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:54:35.204] luat:U(21870):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:54:35.204] luat:U(21871):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:54:35.204] luat:U(21871):I/user.FOTA_DATA 处理分段数据,包序号: 1
+[2025-12-17 16:54:35.204] luat:U(21871):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:54:35.204] luat:U(21871):I/user.FOTA_DATA 数据写入成功,当前总计: 400 字节
+[2025-12-17 16:54:35.345] luat:U(22005):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:35.345] luat:U(22005):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:35.345] luat:U(22005):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:35.345] luat:U(22005):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:54:35.345] luat:U(22006):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:54:35.345] luat:U(22006):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 16:54:35.345] luat:U(22007):I/user.ble_packet_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 16:54:35.345] luat:U(22007):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:54:35.345] luat:U(22008):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:54:35.345] luat:U(22008):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:54:35.345] luat:U(22008):I/user.FOTA_DATA 处理分段数据,包序号: 2
+[2025-12-17 16:54:35.345] luat:U(22008):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:54:35.345] luat:U(22009):I/user.FOTA_DATA 数据写入成功,当前总计: 600 字节
+[2025-12-17 16:54:35.437] luat:U(22110):I/user.fota version 001.000.000
+[2025-12-17 16:54:35.484] luat:U(22151):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:35.484] luat:U(22151):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:35.484] luat:U(22152):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:35.484] luat:U(22152):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:54:35.484] luat:U(22152):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:54:35.484] luat:U(22153):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 16:54:35.484] luat:U(22153):I/user.ble_packet_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 16:54:35.484] luat:U(22154):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:54:35.484] luat:U(22154):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:54:35.484] luat:U(22154):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:54:35.484] luat:U(22154):I/user.FOTA_DATA 处理分段数据,包序号: 3
+[2025-12-17 16:54:35.484] luat:U(22155):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:54:35.484] luat:U(22155):I/user.FOTA_DATA 数据写入成功,当前总计: 800 字节
+[2025-12-17 16:54:35.640] luat:U(22326):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:54:35.640] luat:U(22326):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:54:35.640] luat:U(22327):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:54:35.640] luat:U(22327):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:54:35.640] luat:U(22327):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:54:35.640] luat:U(22328):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 16:54:35.662] luat:U(22328):I/user.ble_packet_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 16:54:35.662] luat:U(22329):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:54:35.662] luat:U(22329):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:54:35.662] luat:U(22329):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:54:35.662] luat:U(22329):I/user.FOTA_DATA 处理分段数据,包序号: 4
+[2025-12-17 16:54:35.662] luat:U(22330):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:54:35.662] luat:U(22330):I/user.FOTA_DATA 数据写入成功,当前总计: 1000 字节
+
+--省略重复日志--
+
+[2025-12-17 16:55:13.372] luat:U(60038):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:55:13.372] luat:U(60038):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:55:13.372] luat:U(60038):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:55:13.372] luat:U(60038):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:55:13.372] luat:U(60039):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:55:13.372] luat:U(60039):I/user.BLE 收到写请求: F000 F002 6E744279496428276C6F67696E506167A9CA6527292E636C6173734C6973742E616464282768696464656E27293B0D0A202066E4202020202020202020202020202020202020646F63756D656E742E676574456C4B87656D656E7442794964282766696C655061676527292E636C6173734C6973742EAEB172656D6F7665282768696464656E27293B0D0A202020202020202020202020207228202020202020206C6F616446696C657328272F6C7561646227293B0D0A202020B82D202020202020202020202020
+[2025-12-17 16:55:13.372] luat:U(60040):I/user.ble_packet_fota 处理写入数据 F000 F002 6E744279496428276C6F67696E506167A9CA6527292E636C6173734C6973742E616464282768696464656E27293B0D0A202066E4202020202020202020202020202020202020646F63756D656E742E676574456C4B87656D656E7442794964282766696C655061676527292E636C6173734C6973742EAEB172656D6F7665282768696464656E27293B0D0A202020202020202020202020207228202020202020206C6F616446696C657328272F6C7561646227293B0D0A202020B82D202020202020202020202020 400
+[2025-12-17 16:55:13.372] luat:U(60040):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:55:13.372] luat:U(60040):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:55:13.372] luat:U(60041):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:55:13.372] luat:U(60041):I/user.FOTA_DATA 处理分段数据,包序号: 214
+[2025-12-17 16:55:13.372] luat:U(60041):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:55:13.372] luat:U(60041):I/user.FOTA_DATA 数据写入成功,当前总计: 43000 字节
+[2025-12-17 16:55:13.528] luat:U(60213):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:55:13.528] luat:U(60213):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:55:13.528] luat:U(60213):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:55:13.528] luat:U(60213):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:55:13.528] luat:U(60214):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:55:13.528] luat:U(60214):I/user.BLE 收到写请求: F000 F002 207D0D0A2020202020202020202020207D293B0D2DB30A20202020202020207D3B0D0A202020203C2F7363726970743E0D0A3C2F626FB49164793E0D0A3C2F68746D6C3E0D0A01045AA55AA502142E6169726D326D5F616C41256C5F637263232E62696E030410000000FE025F0A85779E0F60399D926FD45C9203CFFE06FEBA00000000000000000000000000000000000000000000000000000000E548000000000000000000000000000000000000000000000000000000000000000080290000000000000000
+[2025-12-17 16:55:13.528] luat:U(60215):I/user.ble_packet_fota 处理写入数据 F000 F002 207D0D0A2020202020202020202020207D293B0D2DB30A20202020202020207D3B0D0A202020203C2F7363726970743E0D0A3C2F626FB49164793E0D0A3C2F68746D6C3E0D0A01045AA55AA502142E6169726D326D5F616C41256C5F637263232E62696E030410000000FE025F0A85779E0F60399D926FD45C9203CFFE06FEBA00000000000000000000000000000000000000000000000000000000E548000000000000000000000000000000000000000000000000000000000000000080290000000000000000 400
+[2025-12-17 16:55:13.528] luat:U(60215):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:55:13.528] luat:U(60216):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:55:13.528] luat:U(60216):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:55:13.528] luat:U(60216):I/user.FOTA_DATA 处理分段数据,包序号: 215
+[2025-12-17 16:55:13.563] luat:U(60216):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:55:13.563] luat:U(60216):I/user.FOTA_DATA 数据写入成功,当前总计: 43200 字节
+[2025-12-17 16:55:13.714] luat:U(60388):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:55:13.714] luat:U(60388):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:55:13.714] luat:U(60388):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:55:13.714] luat:U(60388):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:55:13.714] luat:U(60389):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 16:55:13.714] luat:U(60389):I/user.BLE 收到写请求: F000 F002 0000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000
+[2025-12-17 16:55:13.734] luat:U(60390):I/user.ble_packet_fota 处理写入数据 F000 F002 0000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000 400
+[2025-12-17 16:55:13.734] luat:U(60390):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:55:13.734] luat:U(60391):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:55:13.734] luat:U(60391):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 16:55:13.734] luat:U(60391):I/user.FOTA_DATA 处理分段数据,包序号: 216
+[2025-12-17 16:55:13.734] luat:U(60391):I/user.FOTA_DATA 分段写入结果: result: true isDone: false
+[2025-12-17 16:55:13.734] luat:U(60391):I/user.FOTA_DATA 数据写入成功,当前总计: 43400 字节
+[2025-12-17 16:55:13.870] luat:U(60559):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:55:13.870] luat:U(60560):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:55:13.870] luat:U(60560):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:55:13.870] luat:U(60560):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 16:55:13.870] luat:U(60560):I/user.BLE_EVENT 数据长度: 64 字节
+[2025-12-17 16:55:13.870] luat:U(60561):I/user.BLE 收到写请求: F000 F002 00000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029
+[2025-12-17 16:55:13.898] luat:U(60561):I/user.ble_packet_fota 处理写入数据 F000 F002 00000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029 128
+[2025-12-17 16:55:13.898] luat:U(60562):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 16:55:13.910] luat:U(60562):I/user.ble_packet_fota 数据特征值匹配,处理数据
+[2025-12-17 16:55:13.910] luat:U(60562):I/user.FOTA_DATA 收到数据包,长度: 64 字节
+[2025-12-17 16:55:13.923] luat:U(60562):I/user.FOTA_DATA 处理分段数据,包序号: 217
+[2025-12-17 16:55:14.183] luat:I(60853):fota:erase time used 290ms
+[2025-12-17 16:55:14.230] luat:I(60897):fota:write time used 44ms
+[2025-12-17 16:55:14.230] luat:I(60897):fota:FOTA progress: 43464 bytes written , total 40254ms
+[2025-12-17 16:55:14.230] luat:U(60898):I/user.FOTA_DATA 分段写入结果: result: true isDone: true
+[2025-12-17 16:55:14.230] luat:U(60898):I/user.FOTA_DATA 升级进度: 100 % ( 43464 / 43464 )
+[2025-12-17 16:55:14.230] luat:U(60898):I/user.FOTA_DATA 数据写入成功,当前总计: 43464 字节
+[2025-12-17 16:55:14.230] luat:U(60899):I/user.FOTA_DATA 所有数据已接收,等待升级完成...
+[2025-12-17 16:55:14.324] luat:U(60996):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 16:55:14.324] luat:U(60996):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 16:55:14.324] luat:U(60996):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 16:55:14.324] luat:U(60996):I/user.BLE_EVENT 特征值UUID: F001
+[2025-12-17 16:55:14.324] luat:U(60997):I/user.BLE_EVENT 数据长度: 1 字节
+[2025-12-17 16:55:14.324] luat:U(60997):I/user.BLE 收到写请求: F000 F001 02
+[2025-12-17 16:55:14.324] luat:U(60997):I/user.ble_packet_fota 处理写入数据 F000 F001 02 2
+[2025-12-17 16:55:14.324] luat:U(60998):I/user.ble_packet_fota UUID匹配结果: 服务匹配: 1 命令匹配: 1 数据匹配: nil
+[2025-12-17 16:55:14.324] luat:U(60998):I/user.ble_packet_fota 命令特征值匹配,处理命令
+[2025-12-17 16:55:14.324] luat:U(60998):I/user.FOTA_CMD 收到命令数据: 02 长度: 1
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 解析命令码: 2 (0x02)
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 处理结束升级命令
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 验证文件完整性...
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 已接收: 43464 字节
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 应接收: 43464 字节
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 文件完整性验证通过
+[2025-12-17 16:55:14.324] luat:U(60999):I/user.FOTA_CMD 升级数据已全部接收,等待升级完成...
+[2025-12-17 16:55:14.432] luat:I(61099):fota:fota done, write 43464 bytes
+[2025-12-17 16:55:14.432] luat:U(61099):I/user.FOTA_CMD FOTA升级成功!
+[2025-12-17 16:55:14.432] luat:U(61100):I/user.FOTA_CMD 2秒后设备将自动重启...,重启后通过日志判断最终是否升级成功
+[2025-12-17 16:55:14.432] luat:U(61100):I/user.FOTA_CMD 结束升级命令处理完成
+[2025-12-17 16:55:14.432] luat:U(61109):I/user.fota version 001.000.000
+[2025-12-17 16:55:16.430] luat:U(63099):I/user.FOTA_CMD 正在重启设备...
+```
+
+
+
+file方式升级日志如下:
+```lua
+[2025-12-17 17:18:56.849] luat:U(1246):I/user.BLE_INIT 开始初始化BLE...
+[2025-12-17 17:18:56.849] luat:D(1246):bluetooth:执行bt初始化
+[2025-12-17 17:18:56.849] bt_ipc:W(1245):bt_ipc_init bt ipc already initialised
+[2025-12-17 17:18:56.849] rwnx_cal_set_rfconfig(0x101) phy on; rf off
+[2025-12-17 17:18:56.927] luat:U(1323):I/user.BLE_INIT 蓝牙核心初始化成功
+[2025-12-17 17:18:56.927] luat:D(1323):bluetooth:cmd:27
+[2025-12-17 17:18:56.927] luat:U(1324):I/user.BLE_INIT BLE功能初始化成功
+[2025-12-17 17:18:56.927] luat:U(1325):I/user.BLE_INIT GATT服务创建成功
+[2025-12-17 17:18:56.927] luat:U(1325):I/user.BLE_INIT 配置广播数据...
+[2025-12-17 17:18:56.927] luat:D(1331):bluetooth:ble_cmd_cb cmd:1 status:0
+[2025-12-17 17:18:56.961] luat:D(1338):bluetooth:ble_cmd_cb cmd:2 status:0
+[2025-12-17 17:18:56.961] luat:U(1339):I/user.BLE_INIT 广播配置成功
+[2025-12-17 17:18:56.961] luat:D(1345):bluetooth:ble_cmd_cb cmd:4 status:0
+[2025-12-17 17:18:56.961] luat:U(1346):I/user.BLE_INIT BLE广播已启动,设备名称: Air8101_FOTA
+[2025-12-17 17:18:56.961] luat:U(1348):I/user.BLE_EVENT 收到BLE事件: 1
+[2025-12-17 17:18:56.961] luat:U(1349):I/user.BLE_EVENT 其他事件类型: 1
+[2025-12-17 17:18:56.961] luat:U(1349):I/user.BLE_EVENT 收到BLE事件: 3
+[2025-12-17 17:18:56.961] luat:U(1349):I/user.BLE_EVENT 其他事件类型: 3
+[2025-12-17 17:18:56.961] luat:U(1349):I/user.BLE_EVENT 收到BLE事件: 4
+[2025-12-17 17:18:56.961] luat:U(1350):I/user.BLE_EVENT 其他事件类型: 4
+[2025-12-17 17:18:59.706] luat:U(4105):I/user.fota version 001.000.000
+[2025-12-17 17:19:02.699] luat:U(7105):I/user.fota version 001.000.000
+[2025-12-17 17:19:05.704] luat:U(10105):I/user.fota version 001.000.000
+[2025-12-17 17:19:08.695] luat:U(13105):I/user.fota version 001.000.000
+[2025-12-17 17:19:11.706] luat:U(16105):I/user.fota version 001.000.000
+[2025-12-17 17:19:14.707] luat:U(19105):I/user.fota version 001.000.000
+[2025-12-17 17:19:15.805] ap0:ble:W(20098):appm_msg_handler,src_task_id:0x11,dest_id:0x0,src_id:0x0,msgid:0x1104
+[2025-12-17 17:19:15.805] ap0:ble:W(20098):[gapc_connection_req_ind_handler]conidx:0,dest_id:0x0,con_peer_addr:92:6d:c7:33:13:14
+[2025-12-17 17:19:15.805] luat:U(20206):I/user.BLE_EVENT 收到BLE事件: 12
+[2025-12-17 17:19:15.805] luat:U(20206):I/user.BLE_EVENT 设备已连接 地址: 141333C76D92
+[2025-12-17 17:19:15.805] luat:U(20207):I/user.BLE 设备连接成功: 141333C76D92
+[2025-12-17 17:19:16.833] luat:U(21237):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:16.833] luat:U(21237):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:16.833] luat:U(21237):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:16.833] luat:U(21237):I/user.BLE_EVENT 特征值UUID: F001
+[2025-12-17 17:19:16.833] luat:U(21238):I/user.BLE_EVENT 数据长度: 5 字节
+[2025-12-17 17:19:16.833] luat:U(21238):I/user.BLE 收到写请求: F000 F001 01C8A90000
+[2025-12-17 17:19:16.833] luat:U(21239):I/user.ble_file_fota 处理写入数据 F000 F001 01C8A90000 10
+[2025-12-17 17:19:16.833] luat:U(21239):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: 1 数据匹配: nil
+[2025-12-17 17:19:16.833] luat:U(21239):I/user.ble_file_fota 命令特征值匹配,处理命令
+[2025-12-17 17:19:16.833] luat:U(21239):I/user.FOTA_CMD 收到命令数据: 01C8A90000 长度: 5
+[2025-12-17 17:19:16.833] luat:U(21240):I/user.FOTA_CMD 解析命令码: 1 (0x01)
+[2025-12-17 17:19:16.833] luat:U(21240):I/user.FOTA_CMD 处理开始升级命令
+[2025-12-17 17:19:16.833] luat:U(21240):I/user.FOTA_CMD 文件总大小: 43464 字节
+[2025-12-17 17:19:16.833] luat:U(21240):I/user.FOTA_CMD 初始化FOTA子系统...
+[2025-12-17 17:19:16.833] luat:U(21241):I/user.FOTA_CMD FOTA初始化成功
+[2025-12-17 17:19:16.833] luat:U(21241):I/user.FOTA_CMD FOTA底层准备就绪
+[2025-12-17 17:19:16.872] luat:U(21278):I/user.FOTA_CMD 升级状态已设置 总大小: 43464 临时文件: /ble_fota.bin
+[2025-12-17 17:19:16.872] luat:U(21278):I/user.FOTA_CMD 准备接收固件数据...
+[2025-12-17 17:19:17.705] luat:U(22105):I/user.fota version 001.000.000
+[2025-12-17 17:19:17.909] luat:U(22314):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:17.909] luat:U(22314):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:17.909] luat:U(22314):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:17.909] luat:U(22315):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:17.909] luat:U(22315):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:17.909] luat:U(22315):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 17:19:17.929] luat:U(22316):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 17:19:17.929] luat:U(22317):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:17.929] luat:U(22317):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:17.929] luat:U(22317):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:17.929] luat:U(22317):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:17.943] luat:U(22359):I/user.FOTA_DATA 数据写入成功,当前总计: 200 字节
+[2025-12-17 17:19:18.047] luat:U(22451):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.047] luat:U(22451):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.047] luat:U(22452):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.047] luat:U(22452):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.047] luat:U(22452):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.047] luat:U(22453):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 17:19:18.047] luat:U(22453):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 17:19:18.047] luat:U(22454):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:18.085] luat:U(22454):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:18.085] luat:U(22454):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:18.085] luat:U(22455):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:18.624] luat:U(23023):I/user.FOTA_DATA 数据写入成功,当前总计: 400 字节
+[2025-12-17 17:19:18.624] luat:U(23024):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.624] luat:U(23024):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.624] luat:U(23024):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.624] luat:U(23025):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.624] luat:U(23025):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.624] luat:U(23026):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 17:19:18.624] luat:U(23026):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 17:19:18.624] luat:U(23027):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:18.624] luat:U(23027):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:18.624] luat:U(23027):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:18.624] luat:U(23027):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:18.673] luat:U(23098):I/user.FOTA_DATA 数据写入成功,当前总计: 600 字节
+[2025-12-17 17:19:18.673] luat:U(23099):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.684] luat:U(23099):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.684] luat:U(23099):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.684] luat:U(23099):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.684] luat:U(23100):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.684] luat:U(23100):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 17:19:18.684] luat:U(23101):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 17:19:18.684] luat:U(23101):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:18.684] luat:U(23102):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:18.684] luat:U(23102):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:18.684] luat:U(23102):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:18.779] luat:U(23177):I/user.FOTA_DATA 数据写入成功,当前总计: 800 字节
+[2025-12-17 17:19:18.779] luat:U(23178):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.779] luat:U(23178):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.779] luat:U(23178):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.779] luat:U(23179):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.779] luat:U(23179):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.779] luat:U(23180):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+[2025-12-17 17:19:18.779] luat:U(23180):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 400
+[2025-12-17 17:19:18.779] luat:U(23181):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:18.779] luat:U(23182):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:18.779] luat:U(23182):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:18.779] luat:U(23183):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:18.856] luat:U(23264):I/user.FOTA_DATA 数据写入成功,当前总计: 1000 字节
+[2025-12-17 17:19:18.856] luat:U(23265):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.856] luat:U(23265):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.856] luat:U(23265):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.856] luat:U(23266):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.856] luat:U(23266):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.886] luat:U(23266):I/user.BLE 收到写请求: F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5441554CC0A5000001045AA55AA50202020003041800000004020600FE02340301045AA55AA50212C6EE626C655F66696C655F666F74612E6C75616303045E1B0000FE02A50A1B4C7561195C530019930D0A1A0A0404040808785600000000000000000000002877400113404052626C655F66696C655F666F74612E6C7561000000000000000000010714000000A9720B0000004B0001004A4040804AC040814AC040824A80C1828B0001008A00C283
+[2025-12-17 17:19:18.886] luat:U(23267):I/user.ble_file_fota 处理写入数据 F000 F002 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5441554CC0A5000001045AA55AA50202020003041800000004020600FE02340301045AA55AA50212C6EE626C655F66696C655F666F74612E6C75616303045E1B0000FE02A50A1B4C7561195C530019930D0A1A0A0404040808785600000000000000000000002877400113404052626C655F66696C655F666F74612E6C7561000000000000000000010714000000A9720B0000004B0001004A4040804AC040814AC040824A80C1828B0001008A00C283 400
+[2025-12-17 17:19:18.886] luat:U(23268):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:18.886] luat:U(23268):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:18.886] luat:U(23269):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:18.886] luat:U(23269):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:18.964] luat:U(23370):I/user.FOTA_DATA 数据写入成功,当前总计: 1200 字节
+[2025-12-17 17:19:18.997] luat:U(23404):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:18.997] luat:U(23404):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:18.997] luat:U(23404):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:18.997] luat:U(23405):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:18.997] luat:U(23405):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:18.997] luat:U(23405):I/user.BLE 收到写请求: F000 F002 B76B8A80C2848A00C3858A80C386EC0000002C4100006C810000ACC100000A80818780E0AC0101000A800188260000012600800011000000040D69735F757067726164693D9D6E670100040B746F74616C5F73697A65130000000000000000040E7265636569A4007665645F73697A65040D757067726164655F66696C65040E2F626C655F666F7444D9612E62696E040D736572766963655F75756964040546303030040E636861725F5297757569645F636D64040546303031040F636861725F757569645F6461
+[2025-12-17 17:19:19.032] luat:U(23406):I/user.ble_file_fota 处理写入数据 F000 F002 B76B8A80C2848A00C3858A80C386EC0000002C4100006C810000ACC100000A80818780E0AC0101000A800188260000012600800011000000040D69735F757067726164693D9D6E670100040B746F74616C5F73697A65130000000000000000040E7265636569A4007665645F73697A65040D757067726164655F66696C65040E2F626C655F666F7444D9612E62696E040D736572766963655F75756964040546303030040E636861725F5297757569645F636D64040546303031040F636861725F757569645F6461 400
+[2025-12-17 17:19:19.032] luat:U(23407):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:19.032] luat:U(23407):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:19.032] luat:U(23407):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:19.032] luat:U(23407):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:19.106] luat:U(23509):I/user.FOTA_DATA 数据写入成功,当前总计: 1400 字节
+[2025-12-17 17:19:19.214] luat:U(23623):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:19.214] luat:U(23623):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:19.214] luat:U(23623):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:19.214] luat:U(23623):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:19.214] luat:U(23624):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:19.232] luat:U(23624):I/user.BLE 收到写请求: F000 F002 746104052CB34630303204106D61785F7061636B65745F73697A6513C8000000000000000405050070726F63041070726F635F646973636F6E6E6563740100000001000500000000624C280000002D0000000000030D0000000600400007404000430080002440000106608F80400007C040004100010081400100244080010680410007C041002440800026A48D008000080000000405666F7461040766696E69736804046C6F670405696E666FB93E0409464F54415F434D440416E6ADA3E59CA8E9878DE590AF
+[2025-12-17 17:19:19.232] luat:U(23625):I/user.ble_file_fota 处理写入数据 F000 F002 746104052CB34630303204106D61785F7061636B65745F73697A6513C8000000000000000405050070726F63041070726F635F646973636F6E6E6563740100000001000500000000624C280000002D0000000000030D0000000600400007404000430080002440000106608F80400007C040004100010081400100244080010680410007C041002440800026A48D008000080000000405666F7461040766696E69736804046C6F670405696E666FB93E0409464F54415F434D440416E6ADA3E59CA8E9878DE590AF 400
+[2025-12-17 17:19:19.232] luat:U(23626):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:19.232] luat:U(23626):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:19.232] luat:U(23626):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:19.232] luat:U(23626):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:19.325] luat:U(23735):I/user.FOTA_DATA 数据写入成功,当前总计: 1600 字节
+[2025-12-17 17:19:19.437] luat:U(23841):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:19:19.437] luat:U(23841):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:19:19.437] luat:U(23842):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:19:19.437] luat:U(23842):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:19:19.437] luat:U(23842):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:19:19.437] luat:U(23843):I/user.BLE 收到写请求: F000 F002 E8AEBEE5A4872E2E003F2E040572746F7304077265626F6F74010000000000000000000D0000002A00006479002A0000002A0000002A0000002B0000002B0000002B0000002B0000002B000083EB002C0000002C0000002C0000002D0000000000000001000000055F454E560030490A0000009F00000001000C0C010000460040004740C00081800000C1C000000C01FA41410024810001414101009C010000644000035C0000002080C1001E4001804600C8CD400047C0C10081800000C1000200644080012600
+[2025-12-17 17:19:19.464] luat:U(23843):I/user.ble_file_fota 处理写入数据 F000 F002 E8AEBEE5A4872E2E003F2E040572746F7304077265626F6F74010000000000000000000D0000002A00006479002A0000002A0000002A0000002B0000002B0000002B0000002B0000002B000083EB002C0000002C0000002C0000002D0000000000000001000000055F454E560030490A0000009F00000001000C0C010000460040004740C00081800000C1C000000C01FA41410024810001414101009C010000644000035C0000002080C1001E4001804600C8CD400047C0C10081800000C1000200644080012600 400
+[2025-12-17 17:19:19.464] luat:U(23844):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:19:19.464] luat:U(23844):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:19:19.464] luat:U(23844):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:19:19.464] luat:U(23844):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:19:19.564] luat:U(23963):I/user.FOTA_DATA 数据写入成功,当前总计: 1800 字节
+
+-- 省略重复日志--
+
+[2025-12-17 17:20:05.106] luat:U(69516):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:20:05.106] luat:U(69516):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:20:05.106] luat:U(69516):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:20:05.106] luat:U(69517):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:20:05.106] luat:U(69517):I/user.BLE_EVENT 数据长度: 200 字节
+[2025-12-17 17:20:05.120] luat:U(69517):I/user.BLE 收到写请求: F000 F002 0000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000
+[2025-12-17 17:20:05.120] luat:U(69518):I/user.ble_file_fota 处理写入数据 F000 F002 0000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029000000000000000000000000000000000000000000000000000000000000000080290000000000000000000000000000000000000000000000000000000000000000802900000000 400
+[2025-12-17 17:20:05.120] luat:U(69519):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:20:05.120] luat:U(69519):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:20:05.120] luat:U(69519):I/user.FOTA_DATA 收到数据包,长度: 200 字节
+[2025-12-17 17:20:05.120] luat:U(69519):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:20:05.245] luat:U(69658):I/user.FOTA_DATA 数据写入成功,当前总计: 43400 字节
+[2025-12-17 17:20:05.416] luat:U(69819):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:20:05.416] luat:U(69819):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:20:05.416] luat:U(69819):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:20:05.416] luat:U(69820):I/user.BLE_EVENT 特征值UUID: F002
+[2025-12-17 17:20:05.416] luat:U(69820):I/user.BLE_EVENT 数据长度: 64 字节
+[2025-12-17 17:20:05.416] luat:U(69820):I/user.BLE 收到写请求: F000 F002 00000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029
+[2025-12-17 17:20:05.416] luat:U(69821):I/user.ble_file_fota 处理写入数据 F000 F002 00000000000000000000000000000000000000000000000000000000802900000000000000000000000000000000000000000000000000000000000000008029 128
+[2025-12-17 17:20:05.416] luat:U(69821):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: nil 数据匹配: 1
+[2025-12-17 17:20:05.416] luat:U(69821):I/user.ble_file_fota 数据特征值匹配,处理数据
+[2025-12-17 17:20:05.416] luat:U(69822):I/user.FOTA_DATA 收到数据包,长度: 64 字节
+[2025-12-17 17:20:05.416] luat:U(69822):I/user.FOTA_DATA 写入文件: /ble_fota.bin
+[2025-12-17 17:20:05.556] luat:U(69962):I/user.FOTA_DATA 升级进度: 100 % ( 43464 / 43464 )
+[2025-12-17 17:20:05.556] luat:U(69962):I/user.FOTA_DATA 数据写入成功,当前总计: 43464 字节
+[2025-12-17 17:20:05.636] luat:U(70036):I/user.BLE_EVENT 收到BLE事件: 15
+[2025-12-17 17:20:05.636] luat:U(70036):I/user.BLE_EVENT 处理写入事件
+[2025-12-17 17:20:05.636] luat:U(70037):I/user.BLE_EVENT 服务UUID: F000
+[2025-12-17 17:20:05.636] luat:U(70037):I/user.BLE_EVENT 特征值UUID: F001
+[2025-12-17 17:20:05.636] luat:U(70037):I/user.BLE_EVENT 数据长度: 1 字节
+[2025-12-17 17:20:05.636] luat:U(70038):I/user.BLE 收到写请求: F000 F001 02
+[2025-12-17 17:20:05.636] luat:U(70038):I/user.ble_file_fota 处理写入数据 F000 F001 02 2
+[2025-12-17 17:20:05.636] luat:U(70038):I/user.ble_file_fota UUID匹配结果: 服务匹配: 1 命令匹配: 1 数据匹配: nil
+[2025-12-17 17:20:05.636] luat:U(70038):I/user.ble_file_fota 命令特征值匹配,处理命令
+[2025-12-17 17:20:05.636] luat:U(70039):I/user.FOTA_CMD 收到命令数据: 02 长度: 1
+[2025-12-17 17:20:05.636] luat:U(70039):I/user.FOTA_CMD 解析命令码: 2 (0x02)
+[2025-12-17 17:20:05.636] luat:U(70039):I/user.FOTA_CMD 处理结束升级命令
+[2025-12-17 17:20:05.636] luat:U(70039):I/user.FOTA_CMD 验证文件完整性...
+[2025-12-17 17:20:05.636] luat:U(70040):I/user.FOTA_CMD 已接收: 43464 字节
+[2025-12-17 17:20:05.636] luat:U(70040):I/user.FOTA_CMD 应接收: 43464 字节
+[2025-12-17 17:20:05.636] luat:U(70040):I/user.FOTA_CMD 文件完整性验证通过
+[2025-12-17 17:20:05.636] luat:U(70040):I/user.FOTA_CMD 开始执行FOTA升级...
+[2025-12-17 17:20:05.685] luat:U(70062):I/user.FOTA_CMD 文件大小: 43464 字节
+[2025-12-17 17:20:05.685] luat:I(70104):fota:升级包类型: type=1, expected_len=43464
+[2025-12-17 17:20:06.257] luat:I(70666):fota:erase time used 316ms
+[2025-12-17 17:20:06.367] luat:I(70792):fota:write time used 125ms
+[2025-12-17 17:20:06.367] luat:I(70793):fota:FOTA progress: 32768 bytes written , total 49552ms
+[2025-12-17 17:20:06.740] luat:I(71144):fota:erase time used 271ms
+[2025-12-17 17:20:06.786] luat:I(71187):fota:write time used 42ms
+[2025-12-17 17:20:06.786] luat:I(71188):fota:FOTA progress: 43464 bytes written , total 49947ms
+[2025-12-17 17:20:06.786] luat:I(71188):fota:fota file write done, call fota.done()
+[2025-12-17 17:20:06.786] luat:I(71188):fota:fota done, write 43464 bytes
+[2025-12-17 17:20:06.786] luat:I(71188):fota:fota done success, call fota.end()
+[2025-12-17 17:20:06.786] luat:U(71188):I/user.FOTA_CMD FOTA升级结果: result: true isDone: true
+[2025-12-17 17:20:06.786] luat:U(71188):I/user.FOTA_CMD  FOTA升级成功!
+[2025-12-17 17:20:06.786] luat:U(71188):I/user.FOTA_CMD 2秒后设备将自动重启...,重启后通过日志判断最终是否升级成功
+[2025-12-17 17:20:06.786] luat:U(71189):I/user.FOTA_CMD 清理升级状态...
+[2025-12-17 17:20:06.808] luat:U(71221):I/user.FOTA_CMD 已删除临时文件
+[2025-12-17 17:20:06.808] luat:E(71221):fota:fota aborted status=10 written=43464
+[2025-12-17 17:20:06.808] luat:U(71221):I/user.FOTA_CMD 升级流程结束
+```
+
+10、两种升级方式设备重启后现象一样,可以看到版本更新以及打印
+```lua
+[2025-12-17 16:51:51.110] luat:U(1773201):I/user.fota version 001.000.001
+[2025-12-17 16:51:51.110] luat:U(1773201):I/user.hezhou ble_fota test
+[2025-12-17 16:51:54.111] luat:U(1776201):I/user.fota version 001.000.001
+[2025-12-17 16:51:54.111] luat:U(1776201):I/user.hezhou ble_fota test
+[2025-12-17 16:51:57.114] luat:U(1779201):I/user.fota version 001.000.001
+[2025-12-17 16:51:57.114] luat:U(1779201):I/user.hezhou ble_fota test
+[2025-12-17 16:52:00.091] luat:U(1782201):I/user.fota version 001.000.001
+[2025-12-17 16:52:00.091] luat:U(1782201):I/user.hezhou ble_fota test
+
+```
+
+11、工具日志如下:
+```lua
+D:\gitee_hz\LuatOS_demo_v2_temp\module\Air8101\demo\fota\ble_fota>python ble_fota_tool.py -f ble_fota.bin
+
+1. 加载固件文件...
+   固件加载完成,大小: 43464 字节
+
+2. 扫描目标设备...
+   正在扫描,请等待...
+   找到匹配设备: Air8101_FOTA (地址: C8:C2:C6:8C:5D:40)
+   选择设备: Air8101_FOTA (地址: C8:C2:C6:8C:5D:40)
+
+3. 建立BLE连接...
+   连接成功,状态: True
+
+4. 发现服务和特征值...
+   找到FOTA服务: 0000f000-0000-1000-8000-00805f9b34fb
+     特征值: 0000f001-0000-1000-8000-00805f9b34fb - 属性: ['write', 'write-without-response']
+       -> 命令特征值 (可写)
+     特征值: 0000f002-0000-1000-8000-00805f9b34fb - 属性: ['write', 'write-without-response']
+       -> 数据特征值 (可写)
+
+5. 发送开始升级命令...
+   连接成功,等待1秒...
+   写入特征值 f001,数据长度: 5 字节
+   开始命令发送完成
+
+6. 分块传输固件数据...
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   进度: 9.2% - 1.1 KB/s - 已发送 20 包 - 预计剩余: 34.3s
+   写入特征值 f002,数据长度: 200 字节
+
+  --省略发送数据的重复日志--
+
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 200 字节
+   写入特征值 f002,数据长度: 64 字节
+   进度: 100.0% - 1.1 KB/s - 已发送 218 包 - 预计剩余: 0.0s
+   数据传输完成! 总时间: 39.3s, 平均速度: 1.1 KB/s
+
+7. 发送结束升级命令...
+   写入特征值 f001,数据长度: 1 字节
+   结束命令发送完成
+
+8. 等待设备处理升级...
+
+==================================================
+升级流程完成! 设备应该正在重启...
+==================================================
+   已断开连接
+
+```

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

@@ -0,0 +1,92 @@
+--[[
+@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)
+    gpio.setup(13, 1) -- TF卡供电控制(AIR8101专用)
+    local mount_ok, mount_err = fatfs.mount(fatfs.SDIO, "/sd", 24 * 1000 * 1000)
+    if mount_ok then
+        log.info("fatfs.mount", "挂载成功", mount_err)
+        local data, err = fatfs.getfree("/sd") -- 获取SD卡剩余空间信息
+        if data then
+            -- table: 若成功会返回table, 否则返回nil
+            -- table 中包含 total_sectors(总扇区数量), free_sectors(空闲扇区数量), total_kb(总字节数,单位kb), free_kb(空闲字节数, 单位kb)
+            log.info("fatfs", "getfree", json.encode(data))
+        else
+            -- err: 导致失败的底层返回值
+            log.info("fatfs", "err", err)
+        end
+        log.info("FOTA_FILE", "=== 开始文件系统升级 ===")
+    else
+        log.error("fatfs.mount", "挂载失败", mount_err)
+        log.error("FOTA_FILE", "SD卡挂载失败,无法进行文件系统升级")
+        -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败
+        fota.finish(false)
+        -- 尝试卸载(如果需要)
+        fatfs.unmount("/sd")
+        return
+    end
+
+    -- 步骤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 = "/sd/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
module/Air8101/demo/fota/fota(使用fota核心库)/fota_uart.bin


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

@@ -0,0 +1,212 @@
+--[[
+@module  fota_uart
+@summary 串口FOTA升级功能模块
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+-- 串口FOTA升级功能
+-- 提供通过串口分段接收升级包数据进行固件升级的功能
+
+用法:
+1. 先把脚本和固件烧录到模块里, 并确认开机正常
+2. 在电脑端操作:进入命令行程序,执行 `python main.py` 进行升级,需要保证升级文件名字为 `fota_uart.bin`,并且和 `main.py` 在同一目录下
+    注意:运行`python main.py`需要确保电脑安装了Python环境。
+3. 观察luatools的输出和main.py的输出
+4. 模块接收正确的升级数据后,会提示1秒后重启
+5. 本demo自带的脚本升级包,仅加了一条打印和修改版本号
+
+串口通讯过程说明
+串口升级采用简单的文本协议进行握手和数据传输控制:
+协议流程:
+1. 上位机发送:#FOTA\n
+2. 设备回复:#FOTA RDY\n
+3. 上位机发送:256字节数据包
+4. 设备回复:#FOTA NEXT\n(请求下一包)
+5. 重复步骤3-4直到所有数据发送完成
+6. 设备回复:#FOTA OK\n(升级成功)
+7. 设备自动重启
+
+注意:
+- 本demo默认是走main uart串口进行升级
+- 升级过程中如果发生错误,串口会自动关闭,设备会重新初始化进入升级模式
+- 升级成功设备会自动重启
+
+本文件没有对外接口,直接在main.lua中require "fota_uart"就可以加载运行;
+]]
+
+-- 定义所需要的UART编号
+uart_id = 1    -- UART1, 通常也是MAIN_UART
+
+-- 全局变量
+local uart_zbuff = nil
+local uart_fota_state = 0
+local uart_rx_counter = 0
+local uart_fota_writed = 0
+
+-- 初始化串口 - 开机时自动执行
+local function init_uart_fota()
+    log.info("FOTA_UART", "开机自动启动串口升级模式")
+    -- 初始化串口和缓冲区
+    uart_zbuff = zbuff.create(1024)
+    uart.setup(uart_id, 115200)
+    uart.on(uart_id, "receive", uart_cbfun)
+    uart_fota_state = 0
+    uart_rx_counter = 0
+    uart_fota_writed = 0
+    -- 发布事件,唤醒升级任务
+    sys.publish("UART_UPGRADE_START")
+end
+
+-- 清理资源的函数
+local function cleanup_resources()
+    if uart_zbuff then
+        uart_zbuff:del()
+        uart_zbuff = nil
+    end
+    uart.close(uart_id)  -- 关闭串口
+    uart_fota_state = 0
+    log.info("FOTA_UART", "资源已清理,串口已关闭")
+end
+
+-- 串口接收回调函数
+function uart_cbfun(id, len)
+    if not uart_zbuff then
+        return
+    end
+
+    -- 防御缓冲区超标的情况
+    if uart_zbuff:used() > 8192 then
+        log.warn("fota", "uart_zbuff待处理的数据太多了,强制清空")
+        uart_zbuff:del()
+    end
+
+    while true 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")
+            log.info("UART_FOTA", "首次收到数据,发布升级信号")
+        end
+    end
+end
+
+-- 串口升级任务
+local function uartUpgradeTask()
+    local fota_state = 0 -- 0还没开始, 1进行中
+
+    -- 开机时自动启动升级模式
+    init_uart_fota()
+
+    while true do
+        -- 等待升级启动信号
+        sys.waitUntil("UART_UPGRADE_START")
+        log.info("FOTA_UART", "升级任务已启动,等待数据...")
+
+        while true do
+            -- 等待升级数据到来
+            sys.waitUntil("UART_FOTA")
+            log.info("UART_FOTA", "等待数据...")
+
+            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
+
+        -- 升级流程结束后,重新初始化串口等待下次升级
+        sys.wait(1000)
+        init_uart_fota()
+    end
+end
+
+-- 启动串口升级任务
+sys.taskInit(uartUpgradeTask)

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

@@ -0,0 +1,96 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  孟伟
+@usage
+Air8101模块的两种FOTA升级方式:文件系统直接升级和串口分段升级;
+
+分两种不同的应用场景来演示固件升级的实现方法:
+
+1、文件系统直接升级:挂载sd卡文件系统后,通过读取文件系统中的文件直接升级;
+
+2、分段升级:通过串口将升级包文件分多个片段发送,每个片段接收并写入,代码演示使用uart1串口分段写入升级包升级;
+
+适用场景:
+    - 非标准数据传输 -> 串口、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 = "001.000.000" --不同于使用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 true do
+        sys.wait(1000)
+        log.info("fota", "version", VERSION)
+        -- log.info("8101fota test")
+    end
+end
+sys.taskInit(print_version)
+
+-- 方式1: sd卡文件系统直接升级
+require("fota_file")
+
+-- 方式2: 分段写入升级,以串口来分段写入升级包
+-- require("fota_uart")
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 42 - 0
module/Air8101/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(1024):
+                    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)

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

@@ -0,0 +1,542 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、fota_file.lua:介绍了使用文件系统进行FOTA升级功能的实现模块,包括完整的升级流程,文件系统是挂载的sd卡文件系统;
+
+3、fota_uart.lua:介绍了使用串口分段进行FOTA升级功能的实现模块,包括完整的升级流程;
+
+4、main.py:Python脚本工具,用于通过串口分段发送升级包,演示分段升级的流程;
+
+5、fota_uart.bin:演示串口分段升级的升级包文件,升级内容仅升级版本号以及添加几行打印;
+
+## 演示功能概述
+
+FOTA是固件远程升级的简称,用于设备固件的远程更新和维护;
+
+本demo演示的核心功能为:
+
+Air8101模块的两种FOTA升级方式:文件系统直接升级和串口分段升级;
+
+分两种不同的应用场景来演示固件升级的实现方法:
+
+1、文件系统直接升级:通过模组文件系统中的文件直接升级,代码演示通过luatools的烧录文件系统功能将升级包文件直接烧录到文件系统然后升级;
+
+2、分段升级:通过串口将升级包文件分多个片段发送,每个片段接收并写入,代码演示使用usb虚拟串口分段写入升级包升级;
+
+适用场景:
+
+    非标准数据传输 -> 串口、TCP、MQTT等自定义通道升级
+
+    流程精细控制 -> 需要自定义升级前后处理逻辑
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air8101/luatos/app/socket/http/image/RsjSbrZAookedIxzxJAcfYuFnfe.png)
+
+1、Air8101核心板一块;
+
+2、闪迪C10高速TF卡一张(即micro SD卡,即微型SD卡)
+
+3、AirMICROSD_1000配件板一块;
+
+4、TYPE-C USB数据线一根 + USB转串口数据线一根,Air8101核心板和数据线的硬件接线方式为:
+
+- Air8101核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+5、Air8101核心板与AirMICROSD_1000配件板直插,对应管脚为
+| Air8101/Air6101核心板 | AirMICROSD_1000配件板 |
+| ------------- | ----------------- |
+| 59/3V3        | 3V3               |
+| gnd           | gnd               |
+| 9/GPIO6       | CD                |
+| 67/GPIO4      | D0                |
+| 66/GPIO3      | CMD               |
+| 65/GPIO2      | CLK               |
+
+![](https://docs.openluat.com/air8101/luatos/app/driver/sdcard/image/1c02c13d0001814bcaaf2f14d0b1ab20.png)
+
+## 演示软件环境
+
+1、Luatools下载调试工具,需要注意的是luatools工具版本必须为3.1.10及以上版本,否则制作的升级包没办法升级。
+
+2、[Air8101 V2001版本固件以上,25/12/17日后的固件)](https://docs.openluat.com/air8101/luatos/firmware/)
+
+3、Python 3 环境(用于运行main.py发送升级包)
+
+## 演示操作步骤
+
+### 方式1:文件系统直接升级
+
+1、搭建好演示硬件环境
+
+2、修改demo脚本代码,取消`main.lua`中`require("fota_file")`的注释,注释掉`require("fota_uart")`
+
+3、使用Luatools制作升级包,然后将升级包文件放到sd卡中,然后插入AirMICROSD_1000配件板中,然后将配件版链接Air8101核心板。
+
+4、Luatools烧录内核固件和修改前的demo脚本代码。烧录成功后,开机运行。
+
+5、可以看到如下日志:
+日志分析如下:
+
+1. 开始升级,读取文件系统目录下的升级包文件/sd/update.bin
+
+2. FOTA初始化 → 底层就绪 → 文件写入 → MD5校验通过
+
+3. 升级完成,版本验证成功
+
+4. 设备自动重启
+
+5. 新版本 1.0.1 正常运行,新增日志确认升级成功
+
+结果:文件系统FOTA升级完全成功,版本从1.0.0升级到1.0.1
+
+```lua
+[2025-12-19 15:12:02.893] luat:I(581):pm:reset native reason: 0
+[2025-12-19 15:12:02.893] luat:D(581):pm:boot up by power on
+[2025-12-19 15:12:02.893] luat:D(581):pm:poweron reason 0
+[2025-12-19 15:12:02.893] luat:D(582):main:STA MAC: C8C2C68CD602
+[2025-12-19 15:12:02.893] luat:D(583):main:AP  MAC: C8C2C68CD603
+[2025-12-19 15:12:02.893] ap1:mac:W(474):sync_base_mac_record, saved records is more than 1, free index(3).
+[2025-12-19 15:12:02.893] luat:D(587):main:BLE MAC: C8C2C68CD604
+[2025-12-19 15:12:02.893] luat:D(587):main:io voltage set to 3.3V
+[2025-12-19 15:12:02.900] luat:D(587):main:UID: 54540D5E2E
+[2025-12-19 15:12:02.900] luat:I(595):main:LuatOS@Air8101 base 25.11 bsp V2001 64bit
+[2025-12-19 15:12:02.900] luat:I(595):main:ROM Build: Dec 17 2025 13:49:18
+[2025-12-19 15:12:03.063] luat:W(753):pins:/luadb/pins_air8101.json not exist!!
+[2025-12-19 15:12:03.063] luat:D(755):main:loadlibs luavm 2097144 20560 20560
+[2025-12-19 15:12:03.063] luat:D(755):main:loadlibs sys   233872 33384 33384
+[2025-12-19 15:12:03.063] luat:D(755):main:loadlibs psram 6291456 51472 69264
+[2025-12-19 15:12:03.123] luat:U(807):I/user.main fota_test 001.000.000
+[2025-12-19 15:12:04.100] luat:U(1808):I/user.fota version 001.000.000
+[2025-12-19 15:12:05.113] luat:U(2808):I/user.fota version 001.000.000
+[2025-12-19 15:12:06.124] luat:U(3808):I/user.fota version 001.000.000
+[2025-12-19 15:12:07.102] luat:U(4808):I/user.fota version 001.000.000
+[2025-12-19 15:12:08.116] luat:U(5808):I/user.fota version 001.000.000
+[2025-12-19 15:12:09.128] luat:U(6808):I/user.fota version 001.000.000
+[2025-12-19 15:12:10.107] luat:U(7808):I/user.fota version 001.000.000
+[2025-12-19 15:12:11.125] luat:U(8808):I/user.fota version 001.000.000
+[2025-12-19 15:12:12.102] luat:U(9808):I/user.fota version 001.000.000
+[2025-12-19 15:12:13.122] luat:U(10808):I/user.fota version 001.000.000
+[2025-12-19 15:12:13.124] luat:D(10826):fatfs:init FatFS at sdio
+[2025-12-19 15:12:13.124] luat:D(10827):sdio:sdio gpio init : clk 2 cmd 3 data0 4 data1 5 data2 10 data3 11
+[2025-12-19 15:12:13.401] luat:I(11085):fatfs:mount success at fat32
+[2025-12-19 15:12:13.401] luat:U(11085):I/user.fatfs.mount 挂载成功 0
+[2025-12-19 15:12:13.401] luat:U(11086):I/user.fatfs mount true
+[2025-12-19 15:12:13.401] luat:U(11086):I/user.fatfs getfree {"free_sectors":31084608,"total_kb":15549952,"free_kb":15542304,"total_sectors":31099904}
+[2025-12-19 15:12:13.401] luat:U(11087):I/user.FOTA_FILE === 开始文件系统升级 ===
+[2025-12-19 15:12:13.401] luat:U(11087):I/user.FOTA_FILE 初始化FOTA...
+[2025-12-19 15:12:13.401] luat:U(11087):I/user.FOTA_FILE 等待底层准备...
+[2025-12-19 15:12:13.401] luat:U(11087):I/user.FOTA_FILE 底层准备就绪
+[2025-12-19 15:12:13.401] luat:U(11088):I/user.FOTA_FILE 开始读取升级文件: /sd/update.bin
+[2025-12-19 15:12:13.416] luat:I(11107):fota:升级包类型: type=1, expected_len=26056
+[2025-12-19 15:12:13.789] luat:I(11475):fota:erase time used 307ms
+[2025-12-19 15:12:13.883] luat:I(11573):fota:write time used 98ms
+[2025-12-19 15:12:13.883] luat:I(11573):fota:FOTA progress: 26056 bytes written , total 486ms
+[2025-12-19 15:12:13.883] luat:I(11573):fota:fota file write done, call fota.done()
+[2025-12-19 15:12:13.883] luat:I(11573):fota:fota done, write 26056 bytes
+[2025-12-19 15:12:13.883] luat:I(11573):fota:fota done success, call fota.end()
+[2025-12-19 15:12:13.883] luat:U(11574):I/user.FOTA_FILE 升级文件写入flash中的fota分区结果 true true 0
+[2025-12-19 15:12:13.883] luat:U(11574):I/user.FOTA_FILE 结束写入fota分区...
+[2025-12-19 15:12:13.883] luat:U(11574):I/user.FOTA_FILE 写入fota分区状态 结果: true 完成: true
+[2025-12-19 15:12:13.883] luat:U(11574):I/user.FOTA_FILE 升级成功,准备重启设备
+[2025-12-19 15:12:14.115] luat:U(11808):I/user.fota version 001.000.000
+[2025-12-19 15:12:15.125] luat:U(12808):I/user.fota version 001.000.000
+[2025-12-19 15:12:15.935] ef:I(3):ENV start address is 0x007FA000, size is 8192 bytes.
+[2025-12-19 15:12:15.935] ef:I(5):EasyFlash V4.1.0 is initialize success.
+[2025-12-19 15:12:15.935] sensor:W(6):uncali sdmadc value:[0 0]
+[2025-12-19 15:12:15.940] cal:W(19):temp in otp is:567
+[2025-12-19 15:12:16.043] xtal_cali:76
+[2025-12-19 15:12:16.043] cal:I(109):idx:40=40+(0),r:54,xtal:76,pwr_gain:a0ab7128
+[2025-12-19 15:12:16.043] cal:I(118):idx:38=40+(-2),r:54,xtal:79,pwr_gain:a0ab7118
+[2025-12-19 15:12:16.383] ap1:ef:I(0):ENV start address is 0x007FC000, size is 8192 bytes.
+[2025-12-19 15:12:16.477] ap0:ef:I(433):EasyFlash V4.1.0 is initialize success.
+[2025-12-19 15:12:16.477] ap1:WDRV:W(436):wdrv_tx_cmd_buffer_init, addr[0]=0x28012b4c, pattern_addr=0x28012b48
+[2025-12-19 15:12:16.481] ap1:WDRV:W(437):wdrv_tx_cmd_buffer_init, addr[1]=0x28012e6c, pattern_addr=0x28012e68
+[2025-12-19 15:12:16.481] ap1:WDRV:W(437):wdrv_tx_cmd_buffer_init, addr[2]=0x2801318c, pattern_addr=0x28013188
+[2025-12-19 15:12:16.481] mac:W(556):sync_base_mac_record, saved records is more than 1, free index(3).
+[2025-12-19 15:12:16.481] CIF:I(558):cif_handle_bk_cmd_lwipmem_addr_req,634,addr:0x2806b300
+[2025-12-19 15:12:16.912] luat:I(14600):pm:reset native reason: 1
+[2025-12-19 15:12:16.912] luat:D(14600):pm:boot up by reboot
+[2025-12-19 15:12:16.912] luat:I(14600):pm:reset reason: SWRESET
+[2025-12-19 15:12:16.912] luat:D(14600):pm:poweron reason 3
+[2025-12-19 15:12:16.912] luat:D(14600):main:STA MAC: C8C2C68CD602
+[2025-12-19 15:12:16.912] luat:D(14601):main:AP  MAC: C8C2C68CD603
+[2025-12-19 15:12:16.921] ap1:mac:W(870):sync_base_mac_record, saved records is more than 1, free index(3).
+[2025-12-19 15:12:16.921] luat:D(14606):main:BLE MAC: C8C2C68CD604
+[2025-12-19 15:12:16.921] luat:D(14606):main:io voltage set to 3.3V
+[2025-12-19 15:12:16.921] luat:D(14606):main:UID: 54540D5E2E
+[2025-12-19 15:12:16.921] luat:I(14613):main:LuatOS@Air8101 base 25.11 bsp V2001 64bit
+[2025-12-19 15:12:16.921] luat:I(14613):main:ROM Build: Dec 17 2025 13:49:18
+[2025-12-19 15:12:17.083] luat:W(14775):pins:/luadb/pins_air8101.json not exist!!
+[2025-12-19 15:12:17.088] luat:D(14777):main:loadlibs luavm 2097144 20560 20560
+[2025-12-19 15:12:17.088] luat:D(14777):main:loadlibs sys   233872 33384 33384
+[2025-12-19 15:12:17.088] luat:D(14777):main:loadlibs psram 6291456 51472 69264
+[2025-12-19 15:12:17.145] luat:U(14829):I/user.main fota_test 001.000.001
+[2025-12-19 15:12:18.154] luat:U(15830):I/user.fota version 001.000.001
+[2025-12-19 15:12:18.154] luat:U(15831):I/user.8101fota test
+[2025-12-19 15:12:18.157] luat:U(15849):I/user.FOTA_UART 开机自动启动串口升级模式
+[2025-12-19 15:12:18.157] luat:D(15851):uart:uart(1) tx pin: 0, rx pin: 1
+[2025-12-19 15:12:18.157] luat:U(15853):I/user.FOTA_UART 升级任务已启动,等待数据...
+[2025-12-19 15:12:19.145] luat:U(16831):I/user.fota version 001.000.001
+[2025-12-19 15:12:19.145] luat:U(16832):I/user.8101fota test
+[2025-12-19 15:12:20.126] luat:U(17831):I/user.fota version 001.000.001
+[2025-12-19 15:12:20.126] luat:U(17832):I/user.8101fota test
+[2025-12-19 15:12:21.150] luat:U(18831):I/user.fota version 001.000.001
+[2025-12-19 15:12:21.150] luat:U(18832):I/user.8101fota test
+```
+
+### 方式2:串口分段升级
+
+1、搭建好演示硬件环境
+
+2、修改demo脚本代码,确保`main.lua`中已注释`require("fota_file")`,取消`require("fota_uart")`的注释
+
+3、使用Luatools制作升级包,先把新旧版本分别生成量产文件,然后再制作升级包,工具上栏 luatOS->固件工具->差分包/整包升级包制作,将制作好的升级包放在main.py同级目录下
+
+4、Luatools烧录内核固件和修改前的demo脚本代码,烧录成功后,自动开机运行。
+
+5、确认设备连接到电脑的串口(虚拟USB串口)
+
+6、运行Python脚本发送升级包:
+    注意修改核心板uart1接的串口线在电脑上的端口号
+
+```lua
+   python main.py -p COM13
+```
+
+7、脚本会自动寻找设备虚拟串口,发送升级命令并传输`fota_uart.bin`文件
+
+8、设备接收并验证升级包,升级成功后会自动重启
+
+9、可以看到如下日志:
+
+串口分段升级日志解读:
+
+1. uart1 串口连接,收到#FOTA起始指令
+
+2. 开始分段接收升级包,每次256字节,累计5751字节
+
+3. 所有数据包写入成功,MD5校验通过
+
+4. 升级完成,重启
+
+5. 重启后新版本1.0.2运行,新增日志确认升级成功
+
+结果:串口FOTA升级完全成功,版本从1.0.0升级到1.0.2。
+```lua
+[2025-12-18 17:10:48.338] luat:U(951):I/user.main fota_test 001.000.000
+[2025-12-18 17:10:49.369] luat:U(1952):I/user.fota version 001.000.000
+[2025-12-18 17:10:49.416] luat:U(2007):I/user.FOTA_UART 开机自动启动串口升级模式
+[2025-12-18 17:10:49.416] luat:D(2008):uart:uart(1) tx pin: 0, rx pin: 1
+[2025-12-18 17:10:49.416] luat:U(2010):I/user.FOTA_UART 升级任务已启动,等待数据...
+[2025-12-18 17:10:50.364] luat:U(2952):I/user.fota version 001.000.000
+[2025-12-18 17:10:51.348] luat:U(3952):I/user.fota version 001.000.000
+[2025-12-18 17:10:52.359] luat:U(4952):I/user.fota version 001.000.000
+[2025-12-18 17:10:53.340] luat:U(5952):I/user.fota version 001.000.000
+[2025-12-18 17:10:54.028] luat:U(6616):I/user.uart 收到数据 6 累计 6
+[2025-12-18 17:10:54.028] luat:U(6616):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:54.028] luat:U(6617):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:54.028] luat:U(6617):I/user.fota 检测到fota起始标记,进入FOTA状态 #FOTA
+[2025-12-18 17:10:54.028]
+[2025-12-18 17:10:54.358] luat:U(6952):I/user.fota version 001.000.000
+[2025-12-18 17:10:55.090] luat:U(7675):I/user.uart 收到数据 544 累计 550
+[2025-12-18 17:10:55.090] luat:U(7675):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:55.090] luat:U(7675):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:55.090] luat:U(7676):I/user.准备写入fota包 544 累计写入 544
+[2025-12-18 17:10:55.090] luat:U(7676):D/user.fota.run true false 1
+[2025-12-18 17:10:55.090] luat:U(7676):I/user.fota 单包写入完成 544 等待下一个包
+[2025-12-18 17:10:55.114] luat:U(7717):I/user.uart 收到数据 480 累计 1030
+[2025-12-18 17:10:55.114] luat:U(7717):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:55.114] luat:U(7717):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:55.114] luat:U(7718):I/user.准备写入fota包 480 累计写入 1024
+[2025-12-18 17:10:55.114] luat:U(7718):D/user.fota.run true false 1
+[2025-12-18 17:10:55.114] luat:U(7718):I/user.fota 单包写入完成 480 等待下一个包
+[2025-12-18 17:10:55.356] luat:U(7952):I/user.fota version 001.000.000
+[2025-12-18 17:10:56.123] luat:U(8733):I/user.uart 收到数据 1024 累计 2054
+[2025-12-18 17:10:56.123] luat:U(8733):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:56.123] luat:U(8734):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:56.123] luat:U(8734):I/user.准备写入fota包 1024 累计写入 2048
+[2025-12-18 17:10:56.123] luat:I(8734):fota:升级包类型: type=1, expected_len=26056
+[2025-12-18 17:10:56.123] luat:U(8734):D/user.fota.run true false 1
+[2025-12-18 17:10:56.123] luat:U(8735):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:10:56.355] luat:U(8952):I/user.fota version 001.000.000
+[2025-12-18 17:10:57.153] luat:U(9747):I/user.uart 收到数据 1024 累计 3078
+[2025-12-18 17:10:57.153] luat:U(9747):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:57.153] luat:U(9747):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:57.153] luat:U(9748):I/user.准备写入fota包 1024 累计写入 3072
+[2025-12-18 17:10:57.153] luat:U(9748):D/user.fota.run true false 1
+[2025-12-18 17:10:57.153] luat:U(9748):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:10:57.355] luat:U(9952):I/user.fota version 001.000.000
+[2025-12-18 17:10:58.165] luat:U(10758):I/user.uart 收到数据 1024 累计 4102
+[2025-12-18 17:10:58.165] luat:U(10758):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:58.165] luat:U(10758):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:58.165] luat:U(10759):I/user.准备写入fota包 1024 累计写入 4096
+[2025-12-18 17:10:58.165] luat:U(10759):D/user.fota.run true false 1
+[2025-12-18 17:10:58.165] luat:U(10759):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:10:58.367] luat:U(10952):I/user.fota version 001.000.000
+[2025-12-18 17:10:59.176] luat:U(11771):I/user.uart 收到数据 1024 累计 5126
+[2025-12-18 17:10:59.176] luat:U(11771):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:10:59.176] luat:U(11772):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:10:59.176] luat:U(11772):I/user.准备写入fota包 1024 累计写入 5120
+[2025-12-18 17:10:59.176] luat:U(11772):D/user.fota.run true false 1
+[2025-12-18 17:10:59.176] luat:U(11772):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:10:59.361] luat:U(11952):I/user.fota version 001.000.000
+[2025-12-18 17:11:00.190] luat:U(12784):I/user.uart 收到数据 1024 累计 6150
+[2025-12-18 17:11:00.190] luat:U(12784):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:00.190] luat:U(12784):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:00.190] luat:U(12785):I/user.准备写入fota包 1024 累计写入 6144
+[2025-12-18 17:11:00.190] luat:U(12785):D/user.fota.run true false 1
+[2025-12-18 17:11:00.190] luat:U(12785):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:00.362] luat:U(12952):I/user.fota version 001.000.000
+[2025-12-18 17:11:01.187] luat:U(13785):I/user.uart 收到数据 1024 累计 7174
+[2025-12-18 17:11:01.187] luat:U(13785):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:01.187] luat:U(13785):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:01.211] luat:U(13786):I/user.准备写入fota包 1024 累计写入 7168
+[2025-12-18 17:11:01.211] luat:U(13786):D/user.fota.run true false 1
+[2025-12-18 17:11:01.211] luat:U(13786):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:01.345] luat:U(13952):I/user.fota version 001.000.000
+[2025-12-18 17:11:02.194] luat:U(14788):I/user.uart 收到数据 1024 累计 8198
+[2025-12-18 17:11:02.194] luat:U(14788):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:02.194] luat:U(14788):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:02.194] luat:U(14789):I/user.准备写入fota包 1024 累计写入 8192
+[2025-12-18 17:11:02.194] luat:U(14789):D/user.fota.run true false 1
+[2025-12-18 17:11:02.194] luat:U(14789):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:02.366] luat:U(14952):I/user.fota version 001.000.000
+[2025-12-18 17:11:03.195] luat:U(15789):I/user.uart 收到数据 1024 累计 9222
+[2025-12-18 17:11:03.195] luat:U(15789):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:03.195] luat:U(15789):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:03.195] luat:U(15790):I/user.准备写入fota包 1024 累计写入 9216
+[2025-12-18 17:11:03.195] luat:U(15790):D/user.fota.run true false 1
+[2025-12-18 17:11:03.195] luat:U(15790):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:03.367] luat:U(15952):I/user.fota version 001.000.000
+[2025-12-18 17:11:04.203] luat:U(16797):I/user.uart 收到数据 1024 累计 10246
+[2025-12-18 17:11:04.203] luat:U(16797):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:04.203] luat:U(16797):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:04.203] luat:U(16798):I/user.准备写入fota包 1024 累计写入 10240
+[2025-12-18 17:11:04.203] luat:U(16798):D/user.fota.run true false 1
+[2025-12-18 17:11:04.203] luat:U(16798):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:04.358] luat:U(16952):I/user.fota version 001.000.000
+[2025-12-18 17:11:05.201] luat:U(17798):I/user.uart 收到数据 1024 累计 11270
+[2025-12-18 17:11:05.201] luat:U(17798):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:05.201] luat:U(17798):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:05.201] luat:U(17799):I/user.准备写入fota包 1024 累计写入 11264
+[2025-12-18 17:11:05.201] luat:U(17799):D/user.fota.run true false 1
+[2025-12-18 17:11:05.201] luat:U(17799):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:05.358] luat:U(17952):I/user.fota version 001.000.000
+[2025-12-18 17:11:06.219] luat:U(18813):I/user.uart 收到数据 1024 累计 12294
+[2025-12-18 17:11:06.219] luat:U(18813):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:06.219] luat:U(18813):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:06.219] luat:U(18814):I/user.准备写入fota包 1024 累计写入 12288
+[2025-12-18 17:11:06.219] luat:U(18814):D/user.fota.run true false 1
+[2025-12-18 17:11:06.219] luat:U(18814):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:06.358] luat:U(18952):I/user.fota version 001.000.000
+[2025-12-18 17:11:07.232] luat:U(19827):I/user.uart 收到数据 1024 累计 13318
+[2025-12-18 17:11:07.232] luat:U(19827):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:07.232] luat:U(19827):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:07.232] luat:U(19828):I/user.准备写入fota包 1024 累计写入 13312
+[2025-12-18 17:11:07.232] luat:U(19828):D/user.fota.run true false 1
+[2025-12-18 17:11:07.232] luat:U(19828):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:07.356] luat:U(19952):I/user.fota version 001.000.000
+[2025-12-18 17:11:08.229] luat:U(20839):I/user.uart 收到数据 1024 累计 14342
+[2025-12-18 17:11:08.229] luat:U(20839):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:08.229] luat:U(20839):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:08.229] luat:U(20840):I/user.准备写入fota包 1024 累计写入 14336
+[2025-12-18 17:11:08.229] luat:U(20840):D/user.fota.run true false 1
+[2025-12-18 17:11:08.229] luat:U(20840):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:08.353] luat:U(20952):I/user.fota version 001.000.000
+[2025-12-18 17:11:09.257] luat:U(21850):I/user.uart 收到数据 1024 累计 15366
+[2025-12-18 17:11:09.257] luat:U(21850):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:09.257] luat:U(21850):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:09.257] luat:U(21851):I/user.准备写入fota包 1024 累计写入 15360
+[2025-12-18 17:11:09.257] luat:U(21851):D/user.fota.run true false 1
+[2025-12-18 17:11:09.257] luat:U(21851):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:09.366] luat:U(21952):I/user.fota version 001.000.000
+[2025-12-18 17:11:10.272] luat:U(22866):I/user.uart 收到数据 1024 累计 16390
+[2025-12-18 17:11:10.272] luat:U(22866):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:10.272] luat:U(22866):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:10.272] luat:U(22867):I/user.准备写入fota包 1024 累计写入 16384
+[2025-12-18 17:11:10.272] luat:U(22867):D/user.fota.run true false 1
+[2025-12-18 17:11:10.272] luat:U(22867):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:10.362] luat:U(22952):I/user.fota version 001.000.000
+[2025-12-18 17:11:11.284] luat:U(23880):I/user.uart 收到数据 1024 累计 17414
+[2025-12-18 17:11:11.284] luat:U(23880):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:11.284] luat:U(23880):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:11.284] luat:U(23881):I/user.准备写入fota包 1024 累计写入 17408
+[2025-12-18 17:11:11.284] luat:U(23881):D/user.fota.run true false 1
+[2025-12-18 17:11:11.284] luat:U(23881):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:11.362] luat:U(23952):I/user.fota version 001.000.000
+[2025-12-18 17:11:12.284] luat:U(24881):I/user.uart 收到数据 1024 累计 18438
+[2025-12-18 17:11:12.284] luat:U(24881):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:12.284] luat:U(24881):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:12.284] luat:U(24882):I/user.准备写入fota包 1024 累计写入 18432
+[2025-12-18 17:11:12.284] luat:U(24882):D/user.fota.run true false 1
+[2025-12-18 17:11:12.284] luat:U(24882):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:12.363] luat:U(24952):I/user.fota version 001.000.000
+[2025-12-18 17:11:13.299] luat:U(25894):I/user.uart 收到数据 1024 累计 19462
+[2025-12-18 17:11:13.299] luat:U(25894):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:13.299] luat:U(25894):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:13.299] luat:U(25895):I/user.准备写入fota包 1024 累计写入 19456
+[2025-12-18 17:11:13.299] luat:U(25895):D/user.fota.run true false 1
+[2025-12-18 17:11:13.299] luat:U(25895):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:13.361] luat:U(25952):I/user.fota version 001.000.000
+[2025-12-18 17:11:14.300] luat:U(26896):I/user.uart 收到数据 1024 累计 20486
+[2025-12-18 17:11:14.300] luat:U(26896):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:14.300] luat:U(26896):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:14.300] luat:U(26897):I/user.准备写入fota包 1024 累计写入 20480
+[2025-12-18 17:11:14.300] luat:U(26897):D/user.fota.run true false 1
+[2025-12-18 17:11:14.300] luat:U(26897):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:14.361] luat:U(26952):I/user.fota version 001.000.000
+[2025-12-18 17:11:15.301] luat:U(27897):I/user.uart 收到数据 1024 累计 21510
+[2025-12-18 17:11:15.301] luat:U(27897):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:15.301] luat:U(27897):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:15.301] luat:U(27898):I/user.准备写入fota包 1024 累计写入 21504
+[2025-12-18 17:11:15.301] luat:U(27898):D/user.fota.run true false 1
+[2025-12-18 17:11:15.301] luat:U(27898):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:15.362] luat:U(27952):I/user.fota version 001.000.000
+[2025-12-18 17:11:16.306] luat:U(28901):I/user.uart 收到数据 1024 累计 22534
+[2025-12-18 17:11:16.306] luat:U(28901):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:16.306] luat:U(28901):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:16.306] luat:U(28902):I/user.准备写入fota包 1024 累计写入 22528
+[2025-12-18 17:11:16.306] luat:U(28902):D/user.fota.run true false 1
+[2025-12-18 17:11:16.306] luat:U(28902):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:16.367] luat:U(28952):I/user.fota version 001.000.000
+[2025-12-18 17:11:17.309] luat:U(29904):I/user.uart 收到数据 1024 累计 23558
+[2025-12-18 17:11:17.309] luat:U(29904):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:17.309] luat:U(29904):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:17.309] luat:U(29905):I/user.准备写入fota包 1024 累计写入 23552
+[2025-12-18 17:11:17.309] luat:U(29905):D/user.fota.run true false 1
+[2025-12-18 17:11:17.309] luat:U(29905):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:17.355] luat:U(29952):I/user.fota version 001.000.000
+[2025-12-18 17:11:18.312] luat:U(30906):I/user.uart 收到数据 1024 累计 24582
+[2025-12-18 17:11:18.312] luat:U(30906):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:18.312] luat:U(30906):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:18.312] luat:U(30907):I/user.准备写入fota包 1024 累计写入 24576
+[2025-12-18 17:11:18.312] luat:U(30907):D/user.fota.run true false 1
+[2025-12-18 17:11:18.312] luat:U(30907):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:18.338] luat:U(30952):I/user.fota version 001.000.000
+[2025-12-18 17:11:19.326] luat:U(31921):I/user.uart 收到数据 1024 累计 25606
+[2025-12-18 17:11:19.326] luat:U(31921):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:19.326] luat:U(31921):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:19.326] luat:U(31922):I/user.准备写入fota包 1024 累计写入 25600
+[2025-12-18 17:11:19.326] luat:U(31922):D/user.fota.run true false 1
+[2025-12-18 17:11:19.326] luat:U(31922):I/user.fota 单包写入完成 1024 等待下一个包
+[2025-12-18 17:11:19.351] luat:U(31952):I/user.fota version 001.000.000
+[2025-12-18 17:11:20.293] luat:U(32886):I/user.uart 收到数据 456 累计 26062
+[2025-12-18 17:11:20.293] luat:U(32886):I/user.UART_FOTA 首次收到数据,发布升级信号
+[2025-12-18 17:11:20.293] luat:U(32886):I/user.UART_FOTA 等待数据...
+[2025-12-18 17:11:20.293] luat:U(32887):I/user.准备写入fota包 456 累计写入 26056
+[2025-12-18 17:11:20.619] luat:I(33218):fota:erase time used 331ms
+[2025-12-18 17:11:20.729] luat:I(33317):fota:write time used 99ms
+[2025-12-18 17:11:20.729] luat:I(33317):fota:FOTA progress: 26056 bytes written , total 26700ms
+[2025-12-18 17:11:20.729] luat:U(33318):D/user.fota.run true true 0
+[2025-12-18 17:11:20.729] luat:U(33319):I/user.fota version 001.000.000
+[2025-12-18 17:11:20.822] luat:I(33418):fota:fota done, write 26056 bytes
+[2025-12-18 17:11:20.822] luat:U(33418):I/user.fota 已完成,1s后重启
+[2025-12-18 17:11:21.709] luat:U(34318):I/user.fota version 001.000.000
+[2025-12-18 17:11:21.866] ef:I(3):ENV start address is 0x007FA000, size is 8192 bytes.
+[2025-12-18 17:11:21.876] ef:I(5):EasyFlash V4.1.0 is initialize success.
+[2025-12-18 17:11:21.876] sensor:W(6):uncali sdmadc value:[0 0]
+[2025-12-18 17:11:21.876] cal:W(19):temp in otp is:565
+[2025-12-18 17:11:21.975] xtal_cali:76
+[2025-12-18 17:11:21.975] cal:I(105):idx:40=40+(0),r:54,xtal:76,pwr_gain:acab7128
+[2025-12-18 17:11:21.983] cal:I(115):idx:38=40+(-2),r:54,xtal:79,pwr_gain:acab7118
+[2025-12-18 17:11:22.319] ap1:ef:I(0):ENV start address is 0x007FC000, size is 8192 bytes.
+[2025-12-18 17:11:22.411] ap0:ef:I(433):EasyFlash V4.1.0 is initialize success.
+[2025-12-18 17:11:22.416] ap1:WDRV:W(436):wdrv_tx_cmd_buffer_init, addr[0]=0x28012b4c, pattern_addr=0x28012b48
+[2025-12-18 17:11:22.416] ap1:WDRV:W(437):wdrv_tx_cmd_buffer_init, addr[1]=0x28012e6c, pattern_addr=0x28012e68
+[2025-12-18 17:11:22.416] ap1:WDRV:W(437):wdrv_tx_cmd_buffer_init, addr[2]=0x2801318c, pattern_addr=0x28013188
+[2025-12-18 17:11:22.416] mac:W(554):sync_base_mac_record, saved records is more than 1, free index(2).
+[2025-12-18 17:11:22.416] CIF:I(555):cif_handle_bk_cmd_lwipmem_addr_req,634,addr:0x2806b300
+[2025-12-18 17:11:22.880] luat:I(35462):pm:reset native reason: 1
+[2025-12-18 17:11:22.880] luat:D(35462):pm:boot up by reboot
+[2025-12-18 17:11:22.880] luat:I(35462):pm:reset reason: SWRESET
+[2025-12-18 17:11:22.880] luat:D(35462):pm:poweron reason 3
+[2025-12-18 17:11:22.880] luat:D(35463):main:STA MAC: C8C2C68C5D3E
+[2025-12-18 17:11:22.902] luat:D(35464):main:AP  MAC: C8C2C68C5D3F
+[2025-12-18 17:11:22.902] ap1:mac:W(891):sync_base_mac_record, saved records is more than 1, free index(2).
+[2025-12-18 17:11:22.902] luat:D(35468):main:BLE MAC: C8C2C68C5D40
+[2025-12-18 17:11:22.902] luat:D(35468):main:io voltage set to 3.3V
+[2025-12-18 17:11:22.902] luat:D(35468):main:UID: 54540D4935
+[2025-12-18 17:11:22.902] luat:I(35476):main:LuatOS@Air8101 base 25.11 bsp V2001 64bit
+[2025-12-18 17:11:22.902] luat:I(35476):main:ROM Build: Dec 17 2025 13:49:18
+[2025-12-18 17:11:23.134] luat:W(35694):pins:/luadb/pins_air8101.json not exist!!
+[2025-12-18 17:11:23.134] luat:D(35697):main:loadlibs luavm 2097144 20560 20560
+[2025-12-18 17:11:23.134] luat:D(35697):main:loadlibs sys   233872 33384 33384
+[2025-12-18 17:11:23.134] luat:D(35697):main:loadlibs psram 6291456 51472 69264
+[2025-12-18 17:11:23.291] luat:U(35846):I/user.main fota_test 001.000.001
+[2025-12-18 17:11:24.283] luat:U(36847):I/user.fota version 001.000.001
+[2025-12-18 17:11:24.283] luat:U(36847):I/user.8101fota test
+[2025-12-18 17:11:24.342] luat:U(36902):I/user.FOTA_UART 开机自动启动串口升级模式
+[2025-12-18 17:11:24.342] luat:D(36904):uart:uart(1) tx pin: 0, rx pin: 1
+[2025-12-18 17:11:24.342] luat:U(36905):I/user.FOTA_UART 升级任务已启动,等待数据...
+[2025-12-18 17:11:25.296] luat:U(37848):I/user.fota version 001.000.001
+[2025-12-18 17:11:25.296] luat:U(37848):I/user.8101fota test
+[2025-12-18 17:11:26.282] luat:U(38848):I/user.fota version 001.000.001
+[2025-12-18 17:11:26.282] luat:U(38848):I/user.8101fota test
+
+```
+
+main.py 日志:
+```lua
+D:\gitee_hz\LuatOS_demo_v2_temp\module\Air8101\demo\fota\fota(使用fota核心库)>python main.py -p COM13
+使用串口: COM13
+设备响应 b'#FOTA RDY\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 1024
+设备响应 b'#FOTA NEXT\n'
+发送升级包数据 456
+设备响应 b'#FOTA OK\n'
+发送完毕,退出
+```
+
+

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


+ 3 - 3
module/Air8101/demo/fota2/iot_server/air_srv_fota.lua → module/Air8101/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
@@ -94,7 +94,7 @@ local function air_fota_func(data)
 
         -- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
         if "123" == _G.PRODUCT_KEY then
-            while 1 do
+            while true do
                 sys.wait(1000)
                 log.info("fota", "请修改正确的PRODUCT_KEY")
             end

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


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


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


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


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


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


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


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

@@ -15,7 +15,7 @@
 
 ]]
 -- 使用合宙iot平台时需要这个参数
-PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
+PRODUCT_KEY = "BnYk2BlYO30DiWra7q27wUmEarOiipHO" -- 到 iot.openluat.com 创建项目,获取正确的项目id
 --加在libfota2扩展库
 libfota2 = require "libfota2"
 
@@ -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/Air8101/demo/fota2/iot_server/tcp_iot/tcp_iot_main.lua → module/Air8101/demo/fota/fota2(使用libfota2扩展库)/iot_server/tcp_iot/tcp_iot_main.lua


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


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


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

@@ -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
@@ -82,7 +82,7 @@ function fota_task_func()
 
     -- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
     if "123" == _G.PRODUCT_KEY  then
-        while 1 do
+        while true do
             sys.wait(1000)
             log.info("fota", "请修改正确的PRODUCT_KEY")
         end

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


+ 3 - 3
module/Air8101/demo/fota2/self_server/customer_srv_fota.lua → module/Air8101/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/Air8101/demo/fota2/self_server/main.lua → module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/main.lua


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


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


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


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


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


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


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

@@ -15,7 +15,7 @@
 
 ]]
 -- 使用合宙iot平台时需要这个参数
-PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目id
+PRODUCT_KEY = "BnYk2BlYO30DiWra7q27wUmEarOiipHO" -- 到 iot.openluat.com 创建项目,获取正确的项目id
 --加在libfota2扩展库
 libfota2 = require "libfota2"
 
@@ -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
@@ -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/Air8101/demo/fota2/self_server/tcp_self_server/tcp_self_main.lua → module/Air8101/demo/fota/fota2(使用libfota2扩展库)/self_server/tcp_self_server/tcp_self_main.lua


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


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


+ 4 - 4
module/Air8101/demo/fota2/self_server/update.lua → module/Air8101/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
@@ -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()
@@ -109,7 +109,7 @@ function fota_task_func()
     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 1 do
+        while true do
             sys.wait(1000)
             log.info("fota", "当前URL",opts.url,"请修改正确的url")
         end

+ 0 - 131
module/Air8101/demo/fota/main.lua

@@ -1,131 +0,0 @@
--- main.lua文件
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "fota_demo"
-
--- iot限制,只能上传xxx.yyy.zzz格式的三位数的版本号,但实际上现在只用了XXX和ZZZ,中间yyy暂未使用
--- 需要注意的是,因为yyy不生效,所以111.222.333版本和111.444.333版本,对iot平台来说都一样,所以建议中间那一位永远写000
-VERSION = "001.000.000"
-
-
--- 使用合宙iot平台时需要这个参数
-PRODUCT_KEY = "123" -- 到 iot.openluat.com 创建项目,获取正确的项目key(刚刚剪切板里的校验码就填这里)
-
-libfota2 = require "libfota2"
-
--- 联网函数, 可自行删减
-sys.taskInit(function()
-        -----------------------------
-        -- 统一联网函数, 可自行删减
-        ----------------------------
-        if wlan and wlan.connect then
-            -- wifi 联网, Air8101系列均支持
-            local ssid = "Xiaomi_1100"
-            local password = "1234567890"
-            log.info("wifi", ssid, password)
-
-            wlan.init()
-            wlan.setMode(wlan.STATION)
-            wlan.connect(ssid, password, 1)
-            --等待WIFI联网结果,WIFI联网成功后,内核固件会产生一个"IP_READY"消息
-            local result, data = sys.waitUntil("IP_READY")
-            log.info("wlan", "IP_READY", result, data)
-            device_id = wlan.getMac()
-
-        else
-             -- 其他不认识的bsp, 循环提示一下吧
-             while 1 do
-                sys.wait(1000)
-                log.info("bsp", "本bsp可能未适配网络层, 请查证")
-            end
-        end
-        log.info("已联网")
-
-
-        sys.publish("net_ready")
-    end)
-
-
-
--- 循环打印版本号, 方便看版本号变化, 非必须
-sys.taskInit(function()
-    while 1 do
-        sys.wait(5000)
-        log.info("降功耗,找合宙")
-        log.info("fota", "脚本版本号", VERSION, "core版本号", rtos.version())
-    end
-end)
-
--- 升级结果的回调函数
--- 功能:获取fota的回调函数
--- 参数:
--- result:number类型
---   0表示成功
---   1表示连接失败
---   2表示url错误
---   3表示服务器断开
---   4表示接收报文错误
---   5缺少必要的PROJECT_KEY参数
-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.info("接收报文错误", "检查模块固件或升级包内文件是否正常")
-    elseif ret == 5 then
-        log.info("缺少必要的PROJECT_KEY参数")
-    else
-        log.info("不是上面几种情况 ret为",ret)
-    end
-end
-
--- 使用合宙iot平台进行升级,不需要管下面这段代码
--- 使用第三方服务器时打开下面这段代码
-local ota_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://docs.openluat.com/osapi/ext/libfota2/
-}
-
-sys.taskInit(function()
-    -- 这个判断是提醒要设置PRODUCT_KEY的,实际生产请删除
-    if "123" == _G.PRODUCT_KEY and not ota_opts.url then
-        while 1 do
-            sys.wait(1000)
-            log.info("fota", "请修改正确的PRODUCT_KEY")
-        end
-    end
-    -- 等待网络就行后开始检查升级
-    sys.waitUntil("net_ready")
-    log.info("开始检查升级")
-    sys.wait(500)
-    libfota2.request(fota_cb, ota_opts)
-end)
--- 演示定时自动升级, 每隔4小时自动检查一次
-sys.timerLoopStart(libfota2.request, 4 * 3600000, fota_cb, ota_opts)
-
-
-
-
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
-

+ 127 - 0
module/Air8101/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 自由灵活**