Просмотр исходного кода

fix:添加8101的aircloud的demo

mw 2 месяцев назад
Родитель
Сommit
198f71f1b1

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

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

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

@@ -0,0 +1,65 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.22
+@author  孟伟
+@usage
+本demo演示的功能为:
+演示excloud扩展库的使用。
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "excloud_test"
+VERSION = "001.000.000"
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+sys.timerLoopStart(function()
+    log.info("mem.lua", rtos.meminfo())
+    log.info("mem.sys", rtos.meminfo("sys"))
+end, 3000)
+
+-- -- 加载网络驱动设备功能模块
+require "netdrv_device"
+
+-- 加载excloud测试模块
+require"excloud_test"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 118 - 0
module/Air8101/demo/aircloud/netdrv/netdrv_4g.lua

@@ -0,0 +1,118 @@
+--[[
+@module  netdrv_4g
+@summary “通过SPI接口外挂4G模组(Air780EHM/Air780EHV/Air780EGH/Air780EPM)的4G网卡”驱动模块
+@version 1.0
+@date    2025.07.27
+@author  马梦阳
+@usage
+本文件为 “通过SPI接口外挂4G模组(Air780EHM/Air780EHV/Air780EGH/Air780EPM)的4G网卡”驱动模块,核心业务逻辑为:
+1、初始化和外部4G网卡的配置(初始化AirLINK、配置桥接网络、配置SPI、静态配置IP地址/子网掩码/网关);
+2、4G网卡的连接状态发生变化时,在日志中进行打印;
+
+
+硬件环境使用以下两种环境中的一种即可:
+1、Air8101核心板+Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板
+2、Air8101核心板+Air780EHM/Air780EHV/Air780EGH/Air780EPM开发板
+
+一、当使用第1种硬件环境时,Air8101核心板和Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板的硬件接线方式为:
+
+1、Air8101核心板:
+- 核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+- 如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+
+2、Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板:
+- 核心板通过TYPE-C USB口供电(TYPE-C USB口旁边的ON/OFF拨动开关拨到ON一端);
+- 如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的5V管脚进行5V供电;
+
+3、
+| Air8101核心板 |  Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板  |
+| ------------ | ---------------------------------------------- |
+|     gnd      |                     GND                        |
+|  54/DISP     |                     83/SPI0CS                  |
+|  55/HSYN     |                     84/SPI0MISO                |
+|    57/DE     |                     85/SPI0MOSI                |
+|  28/DCLK     |                     86/SPI0CLK                 |
+|    43/R2     |                     19/GPIO22                  |
+|  75/GPIO28   |                     22/GPIO1                   |
+
+二、当使用第2种硬件环境时,Air8101核心板和Air780EHM/Air780EHV/Air780EGH/Air780EPM开发板的硬件接线方式为:
+
+1、Air8101核心板:
+- 核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+- 如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+
+2、Air780EHM/Air780EHV/Air780EGH/Air780EPM开发板:
+- 核心板通过TYPE-C USB口供电(外部供电/USB供电拨动开关拨到USB供电一端);
+- 如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对开发板的5V管脚进行5V供电;
+
+3、
+| Air8101核心板 | Air780EHM/Air780EHV/Air780EGH/Air780EPM开发板  |
+| ------------ | ---------------------------------------------- |
+|     gnd      |                     GND                        |
+|  54/DISP     |                     SPI_CS                     |
+|  55/HSYN     |                     SPI_MISO                   |
+|    57/DE     |                     SPI_MOSI                   |
+|  28/DCLK     |                     SPI_CLK                    |
+|    43/R2     |                     GPIO22                     |
+|  75/GPIO28   |                     GPIO1                      |
+
+三、以上两种硬件环境,Air8101使用的SPI0默认的一组引脚,也可以使用SPI1;使用SPI1时,硬件连接说明的更多资料参考:
+https://docs.openluat.com/air8101/luatos/hardware/design/4gnet/
+软件代码需要做以下配置:
+airlink.config(airlink.CONF_SPI_ID, 1)
+airlink.config(airlink.CONF_SPI_CS, 10)
+
+四、测试本功能模块时,Air780EHM/Air780EHV/Air780EGH/Air780EPM需要烧录以下软件:
+1、最新版本的内核固件
+2、脚本:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/multi_network/WIFI_4G_ETH/Air8101_Air780EPM/Air780EPM_master
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_USER0 then
+        log.info("netdrv_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_USER0))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_USER0 then
+        log.warn("netdrv_4g.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI接口外挂4G模组(Air780EHM/Air780EHV/Air780EGH/Air780EPM)的4G网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_USER0
+socket.dft(socket.LWIP_USER0)
+
+
+-- 初始化airlink,Air8101和4G网卡之间,在spi之上,基于airlink协议通信
+airlink.init()
+-- 创建桥接网络设备
+-- 此处第一个参数必须是socket.LWIP_USER0,是因为Air780EHM/Air780EHV/Air780EGH/Air780EPM使用的也是socket.LWIP_USER0,双方是点对点通讯的对等网络
+-- 此处第二个参数必须是netdrv.WHALE,表示虚拟网卡的实现方式
+netdrv.setup(socket.LWIP_USER0, netdrv.WHALE)
+-- 启动airlink,配置Air8101作为SPI从机模式。
+airlink.start(airlink.MODE_SPI_SLAVE)
+
+-- 静态配置IPv4地址
+-- 本地ip地址为"192.168.111.1",网关ip地址为"192.168.111.2",子网掩码为"255.255.255.0"
+-- 此处设置的本地ip地址要和Air780EHM/Air780EHV/Air780EGH/Air780EPM中设置的网关ip地址完全一样
+-- 此处设置的网关ip地址要和Air780EHM/Air780EHV/Air780EGH/Air780EPM中设置的本地ip地址完全一样
+-- 此处设置的子网掩码要和Air780EHM/Air780EHV/Air780EGH/Air780EPM中设置的子网掩码完全一样
+netdrv.ipv4(socket.LWIP_USER0, "192.168.111.1", "255.255.255.0", "192.168.111.2")
+
+-- 4G联网成功后,内核固件会产生一个"IP_READY"消息
+-- 各个功能模块可以订阅"IP_READY"消息实时处理4G联网成功的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_USER0)来获取4G网络是否连接成功
+
+-- 4G断网后,内核固件会产生一个"IP_LOSE"消息
+-- 各个功能模块可以订阅"IP_LOSE"消息实时处理4G网络断网的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_USER0)来获取4G网络是否连接成功

+ 77 - 0
module/Air8101/demo/aircloud/netdrv/netdrv_eth_rmii.lua

@@ -0,0 +1,77 @@
+--[[
+@module  netdrv_eth_rmii
+@summary “通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”驱动模块 
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”驱动模块 ,核心业务逻辑为:
+1、打开PHY芯片供电开关;
+2、初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+Air8101核心板和AirPHY_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板 |  AirPHY_1000配件板  |
+| ------------ | ------------------ |
+|    59/3V3    |         3.3v       |
+|     gnd      |         gnd        |
+|     5/D2     |         RX1        |
+|    72/D1     |         RX0        |
+|    71/D3     |         CRS        |
+|     4/D0     |         MDIO       |
+|     6/D4     |         TX0        |
+|    74/PCK    |         MDC        |
+|    70/D5     |         TX1        |
+|     7/D6     |         TXEN       |
+|     不接     |          NC        |
+|    69/D7     |         CLK        |
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_rmii"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        log.info("netdrv_eth_rmii.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_rmii.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是核心板的VDD 3V3引脚对AirPHY_1000配件板进行供电
+-- VDD 3V3引脚是Air8101内部的LDO输出引脚,最大输出电流300mA
+-- GPIO13在Air8101内部使能控制这个LDO的输出
+-- 所以在此处GPIO13输出高电平打开这个LDO
+gpio.setup(13, 1, gpio.PULLUP)
+
+
+--初始化以太网卡
+
+--以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+--各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+--以太网断网后,内核固件会产生一个"IP_LOSE"消息
+--各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+netdrv.setup(socket.LWIP_ETH)
+
+--在以太网卡上开启动态主机配置协议
+netdrv.dhcp(socket.LWIP_ETH, true)

+ 102 - 0
module/Air8101/demo/aircloud/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,102 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块 ,核心业务逻辑为:
+1、打开AirETH_1000配件板供电开关;
+2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+Air8101核心板和AirETH_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板    | AirETH_1000配件板  |
+| --------------- | ----------------- |
+| 59/3V3          | 3.3v              |
+| gnd             | gnd               |
+| 28/DCLK         | SCK               |
+| 54/DISP         | CSS               |
+| 55/HSYN         | SDO               |
+| 57/DE           | SDI               |
+| 14/GPIO8        | INT               |
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_USER1 then
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_USER1))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_USER1 then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_USER1
+socket.dft(socket.LWIP_USER1)
+
+
+-- 本demo测试使用的是核心板的VDD 3V3引脚对AirETH_1000配件板进行供电
+-- VDD 3V3引脚是Air8101内部的LDO输出引脚,最大输出电流300mA
+-- GPIO13在Air8101内部使能控制这个LDO的输出
+-- 所以在此处GPIO13输出高电平打开这个LDO
+gpio.setup(13, 1, gpio.PULLUP)
+
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI0
+    local result = spi.setup(
+        0,--spi_id
+        nil,
+        0,--CPHA
+        0,--CPOL
+        8,--数据宽度
+        25600000--,--频率
+        -- spi.MSB,--高低位顺序    可选,默认高位在前
+        -- spi.master,--主模式     可选,默认主
+        -- spi.full--全双工       可选,默认全双工
+    )
+    log.info("netdrv_eth_spi", "spi open result", result)
+    -- 返回值为0,表示打开成功
+    if result ~= 0 then
+        log.error("netdrv_eth_spi", "spi open error",result)
+        return
+    end
+
+    -- 初始化以太网卡
+
+    -- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    -- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    -- 也可以在任何时刻调用socket.adapter(socket.LWIP_USER1)来获取以太网是否连接成功
+
+    -- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    -- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    -- 也可以在任何时刻调用socket.adapter(socket.LWIP_USER1)来获取以太网是否连接成功
+
+    -- socket.LWIP_USER1 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 0, 片选 GPIO15
+    netdrv.setup(socket.LWIP_USER1, netdrv.CH390, {spi=0, cs=15})
+
+    -- 在以太上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_USER1, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 145 - 0
module/Air8101/demo/aircloud/netdrv/netdrv_multiple.lua

@@ -0,0 +1,145 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(WIFI STA网卡、通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡、通过SPI外挂CH390H芯片的以太网卡、通过SPI外挂4G模组的4G网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为多网卡驱动模块 ,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+
+通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡:
+Air8101核心板和AirPHY_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板 | AirPHY_1000配件板  |
+| ------------ | ------------------ |
+|    59/3V3    |         3.3v       |
+|     gnd      |         gnd        |
+|     5/D2     |         RX1        |
+|    72/D1     |         RX0        |
+|    71/D3     |         CRS        |
+|     4/D0     |         MDIO       |
+|     6/D4     |         TX0        |
+|    74/PCK    |         MDC        |
+|    70/D5     |         TX1        |
+|     7/D6     |         TXEN       |
+|     不接     |          NC        |
+|    69/D7     |         CLK        |
+
+
+通过SPI外挂CH390H芯片的以太网卡(此网卡和4G网卡硬件连接有冲突,如果使用以太网,可以优先使用rmii接口的以太网卡,如果必须使用spi以太网卡,注意更换以太网或者4G网卡使用的spi,不要冲突):
+Air8101核心板和AirETH_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板   |  AirETH_1000配件板 |
+| --------------- | ----------------- |
+| 59/3V3          | 3.3v              |
+| gnd             | gnd               |
+| 28/DCLK         | SCK               |
+| 54/DISP         | CSS               |
+| 55/HSYN         | SDO               |
+| 57/DE           | SDI               |
+| 14/GPIO8        | INT               |
+
+
+通过SPI接口外挂4G模组(Air780EHM/Air780EHV/Air780EGH/Air780EPM)的4G网卡:
+Air8101核心板和Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板或者开发板的硬件接线方式,参考netdrv_4g.lua的文件头注释;
+
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)”的以太网卡,可以使用Air8101核心板+AirPHY_1000配件板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO,此demo使用的59脚3V3供电,受GPIO13控制
+                    pwrpin = 13,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+                }
+            },
+
+            -- “通过SPI外挂CH390H芯片”的以太网卡,可以使用Air8101核心板+AirETH_1000配件板验证
+            -- {
+            --     ETHUSER1 = {
+            --         -- 供电使能GPIO,此demo使用的59脚3V3供电,受GPIO13控制
+            --         pwrpin = 13,
+            --         -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+            --         -- 如果没有传入此参数,exnetif会使用默认值10秒
+            --         ping_time = 3000, 
+
+            --         -- 连通性检测ip(选填参数);
+            --         -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+            --         -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+            --         -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+            --         -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+            --         tp = netdrv.CH390, 
+            --         opts = {spi=0, cs=15}
+            --     }
+            -- },
+
+            -- WIFI STA网卡
+            {
+                WIFI = {
+                    -- 要连接的WIFI路由器名称
+                    ssid = "茶室-降功耗,找合宙!",
+                    -- 要连接的WIFI路由器密码
+                    password = "Air123456", 
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+                }
+            }
+        }
+    )    
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

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

@@ -0,0 +1,57 @@
+--[[
+@module  netdrv_pc
+@summary “pc模拟器网卡”驱动模块
+@version 1.0
+@date    2025.07.01
+@author  孟伟
+@usage
+本文件为pc模拟器网卡驱动模块,核心业务逻辑为:
+1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_pc"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.ETH0 then
+        -- 在位置1和2设置自定义的DNS服务器ip地址:
+        -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
+        -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
+        -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
+        -- 如果使用专网卡,不要使用这两行代码;
+        -- 如果使用国外的网络,不要使用这两行代码;
+        socket.setDNS(adapter, 1, "223.5.5.5")
+        socket.setDNS(adapter, 2, "114.114.114.114")
+
+        log.info("netdrv_pc.ip_ready_func", "IP_READY", socket.localIP(socket.ETH0))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.ETH0 then
+        log.warn("netdrv_pc.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察pc模拟器网络的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+-- 设置默认网卡为socket.ETH0
+-- pc模拟器上的默认网卡仍然需要使用接口(socket.ETH0)来设置,因为exnetif扩展库当前还不支持模拟器
+socket.dft(socket.ETH0)
+
+
+
+-- 下面这段代码是在PC模拟器上构造一个唯一的ID;不同PC上运行模拟器,这个ID要不一样
+-- 因为mqtt client使用的是这个ID做为client id,如果不同PC上的id一样,模拟器在不同PC上同时运行时,mqtt client就会频繁出现被踢下线的问题
+-- 目前模拟器上还没有合适的接口获取唯一ID,所以此处先简单的构造一个ID,需要手动保证ID唯一,在此处我简单使用了zhutianhua1做为ID
+_G.mobile = {}
+function mobile.imei()
+    return "zhutianhua1"
+    -- log.info("mcu.unique_id()", mcu.unique_id())
+    -- return mcu.unique_id().."zhutianhua1"
+end

+ 55 - 0
module/Air8101/demo/aircloud/netdrv/netdrv_wifi.lua

@@ -0,0 +1,55 @@
+--[[
+@module  netdrv_wifi
+@summary “WIFI STA网卡”驱动模块
+@version 1.0
+@date    2025.07.01
+@author  马梦阳
+@usage
+本文件为WIFI STA网卡驱动模块,核心业务逻辑为:
+1、初始化WIFI网络;
+2、连接WIFI路由器;
+3、和WIFI路由器之间的连接状态发生变化时,在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_wifi"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_STA then
+        log.info("netdrv_wifi.ip_ready_func", "IP_READY", json.encode(wlan.getInfo()))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_STA then
+        log.warn("netdrv_wifi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察WIFI的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_STA
+-- 在Air8101上,内核固件运行起来之后,默认网卡就是socket.LWIP_STA
+-- 在单socket.LWIP_STA网卡使用场景下,下面这一行代码加不加都没有影响,为了和其他网卡驱动模块的代码风格保持一致,所以加上了
+socket.dft(socket.LWIP_STA)
+
+
+wlan.init()
+-- 连接WIFI热点,连接结果会通过"IP_READY"或者"IP_LOSE"消息通知
+-- Air8101仅支持2.4G的WIFI,不支持5G的WIFI
+-- 此处前两个参数表示WIFI热点名称以及密码,更换为自己测试时的真实参数即可
+-- 第三个参数1表示WIFI连接异常时,内核固件会自动重连
+wlan.connect("vivox200", "00000008", 1)
+
+-- WIFI联网成功(做为STATION成功连接AP,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+-- 各个功能模块可以订阅"IP_READY"消息实时处理WIFI联网成功的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功
+
+-- WIFI断网后,内核固件会产生一个"IP_LOSE"消息
+-- 各个功能模块可以订阅"IP_LOSE"消息实时处理WIFI断网的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功

+ 40 - 0
module/Air8101/demo/aircloud/netdrv_device.lua

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

+ 226 - 0
module/Air8101/demo/aircloud/readme.md

@@ -0,0 +1,226 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单spi以太网卡,单pc模拟器网卡,多网卡)中的任何一种网卡;
+
+3、excloud.lua: aircloud的实现库
+
+4、excloud_test.lua:aircloud的应用模块,实现了aircloud的应用场景。
+
+## 演示功能概述
+
+使用Air8101核心板测试aircloud功能
+
+AirCloud 概述:AirCloud 是 LuatOS 物联网设备云服务通信协议,提供设备连接、数据上报、远程控制和文件上传等核心功能。excloud 扩展库是 AirCloud 协议的实现,通过该库设备可以快速接入云服务平台,实现远程监控和管理。
+
+本demo演示了excloud扩展库的完整使用流程,包括:
+1. 设备连接与认证
+2. 数据上报与接收
+3. 运维日志管理
+4. 文件上传功能
+5. 心跳保活机制
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air8101/luatos/app/image/netdrv_multi.jpg)
+
+1、Air8101核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、USB转串口数据线一根
+
+4、Air8101核心板和数据线的硬件接线方式为
+
+- Air8101核心板通过TYPE-C USB口供电;(核心板背面的功耗测试开关拨到OFF一端)
+
+- 如果测试发现软件频繁重启,重启原因值为:poweron reason 0,可能是供电不足,此时再通过直流稳压电源对核心板的vbat管脚进行4V供电,或者VIN管脚进行5V供电;
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- USB转串口数据线,一般来说,白线连接核心板的12/U1TX,绿线连接核心板的11/U1RX,黑线连接核心板的gnd,另外一端连接电脑USB口;
+
+5、可选AirPHY_1000配件板一块,Air8101核心板和AirPHY_1000配件板的硬件接线方式为:
+
+| Air8101核心板 | AirPHY_1000配件板 |
+| ------------- | ----------------- |
+| 59/3V3        | 3.3v              |
+| gnd           | gnd               |
+| 5/D2          | RX1               |
+| 72/D1         | RX0               |
+| 71/D3         | CRS               |
+| 4/D0          | MDIO              |
+| 6/D4          | TX0               |
+| 74/PCK        | MDC               |
+| 70/D5         | TX1               |
+| 7/D6          | TXEN              |
+| 不接          | NC                |
+| 69/D7         | CLK               |
+
+6、可选AirETH_1000配件板一块,Air8101核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air8101核心板 | AirETH_1000配件板 |
+| ------------- | ----------------- |
+| 59/3V3        | 3.3v              |
+| gnd           | gnd               |
+| 28/DCLK       | SCK               |
+| 54/DISP       | CSS               |
+| 55/HSYN       | SDO               |
+| 57/DE         | SDI               |
+| 14/GPIO8      | INT               |
+
+7、可选Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板或者开发板一块,Air8101核心板和Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板或者开发板的硬件接线方式为:
+
+| Air8101核心板 | Air780EHM/Air780EHV/Air780EGH/Air780EPM核心板 |
+| ------------- | --------------------------------------------- |
+| gnd           | GND                                           |
+| 54/DISP       | 83/SPI0CS                                     |
+| 55/HSYN       | 84/SPI0MISO                                   |
+| 57/DE         | 85/SPI0MOSI                                   |
+| 28/DCLK       | 86/SPI0CLK                                    |
+| 43/R2         | 19/GPIO22                                     |
+| 75/GPIO28     | 22/GPIO1                                      |
+
+
+| Air8101核心板 | Air780EHM/Air780EHV/Air780EGH/Air780EPM开发板 |
+| ------------- | --------------------------------------------- |
+| gnd           | GND                                           |
+| 54/DISP       | SPI_CS                                        |
+| 55/HSYN       | SPI_MISO                                      |
+| 57/DE         | SPI_MOSI                                      |
+| 28/DCLK       | SPI_CLK                                       |
+| 43/R2         | GPIO22                                        |
+| 75/GPIO28     | GPIO1                                         |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8101 V1006版本固件](https://docs.openluat.com/air8101/luatos/firmware/)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+- 如果需要单WIFI STA网卡,打开require "netdrv_wifi",其余注释掉;同时netdrv_wifi.lua中的wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+- 如果需要RMII以太网卡,打开require "netdrv_eth_rmii",其余注释掉
+
+- 如果需要SPI以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+- 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+- 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+3、修改excloud_test.lua文件中excloud.setup接口的相关参数,根据自己需求配置连接协议、是否启用运维日志、项目key、设备类型,是否启用getip等内容。
+
+4、烧录好后,板子开机同时在luatools上查看日志:
+
+```lua
+[2025-11-24 17:34:43.669] $luat:I(125):pm:reset native reason: 0
+[2025-11-24 17:34:43.669] luat:D(125):pm:boot up by power on
+[2025-11-24 17:34:43.669] luat:D(126):pm:poweron reason 0
+[2025-11-24 17:34:43.669] luat:D(126):main:STA MAC: C8C2C68C5D3E
+[2025-11-24 17:34:43.669] luat:D(126):main:AP  MAC: C8C2C68C5D3F
+[2025-11-24 17:34:43.669] luat:D(126):main:BLE MAC: C8C2C68C5D40
+[2025-11-24 17:34:43.669] luat:D(126):main:ETH MAC: C8C2C68C5D41
+[2025-11-24 17:34:43.669] luat:D(126):main:io voltage set to 3.3V
+[2025-11-24 17:34:43.669] luat:I(131):main:LuatOS@Air8101 base 25.03 bsp V1006 32bit
+[2025-11-24 17:34:43.669] luat:I(131):main:ROM Build: Aug 31 2025 19:38:24
+[2025-11-24 17:34:43.669] luat:D(137):main:loadlibs luavm 1572856 16936 16936
+[2025-11-24 17:34:43.669] luat:D(138):main:loadlibs sys   181304 74216 86240
+[2025-11-24 17:34:43.669] luat:D(138):main:loadlibs psram 1572864 46632 46712
+[2025-11-24 17:34:44.153] luat:U(589):W/user.excloud_task_func wait IP_READY 2 2
+[2025-11-24 17:34:44.153] luat:D(594):wlan:event_module 1 event_id 4
+[2025-11-24 17:34:45.085] luat:D(1521):wlan:event_module 1 event_id 4
+[2025-11-24 17:34:45.163] luat:U(1590):W/user.excloud_task_func wait IP_READY 2 2
+[2025-11-24 17:34:45.881] luat:D(2310):wlan:event_module 1 event_id 3
+[2025-11-24 17:34:45.881] luat:D(2310):wlan:STA connected vivox200 
+[2025-11-24 17:34:45.881] luat:D(2310):DHCP:dhcp state 6 2310 0 0
+[2025-11-24 17:34:45.881] luat:D(2311):DHCP:dhcp discover C8C2C68C5D3E
+[2025-11-24 17:34:45.881] luat:I(2311):ulwip:adapter 2 dhcp payload len 308
+[2025-11-24 17:34:46.145] luat:U(2589):W/user.excloud_task_func wait IP_READY 2 2
+[2025-11-24 17:34:46.693] luat:U(3146):I/user.mem.lua 1572856 188432 189696
+[2025-11-24 17:34:46.693] luat:U(3147):I/user.mem.sys 181304 83512 86960
+[2025-11-24 17:34:46.881] luat:D(3309):DHCP:dhcp state 7 3309 0 0
+[2025-11-24 17:34:46.881] luat:D(3310):DHCP:long time no offer, resend
+[2025-11-24 17:34:46.881] luat:I(3310):ulwip:adapter 2 dhcp payload len 308
+[2025-11-24 17:34:46.881] luat:D(3321):ulwip:收到DHCP数据包(len=310)
+[2025-11-24 17:34:46.881] luat:D(3322):DHCP:dhcp state 7 3322 0 0
+[2025-11-24 17:34:46.881] luat:D(3322):DHCP:find ip 7718a8c0 192.168.24.119
+[2025-11-24 17:34:46.881] luat:D(3322):DHCP:result 2
+[2025-11-24 17:34:46.931] luat:D(3322):DHCP:select offer, wait ack
+[2025-11-24 17:34:46.931] luat:I(3322):ulwip:adapter 2 dhcp payload len 302
+[2025-11-24 17:34:46.931] luat:D(3339):ulwip:收到DHCP数据包(len=310)
+[2025-11-24 17:34:46.931] luat:D(3340):DHCP:dhcp state 9 3340 0 0
+[2025-11-24 17:34:46.931] luat:D(3340):DHCP:find ip 7718a8c0 192.168.24.119
+[2025-11-24 17:34:46.931] luat:D(3340):DHCP:result 5
+[2025-11-24 17:34:46.931] luat:D(3340):DHCP:DHCP get ip ready
+[2025-11-24 17:34:46.931] luat:D(3340):ulwip:adapter 2 ip 192.168.24.119
+[2025-11-24 17:34:46.931] luat:D(3340):ulwip:adapter 2 mask 255.255.255.0
+[2025-11-24 17:34:46.931] luat:D(3340):ulwip:adapter 2 gateway 192.168.24.212
+[2025-11-24 17:34:46.931] luat:D(3340):ulwip:adapter 2 lease_time 3599s
+[2025-11-24 17:34:46.931] luat:D(3340):ulwip:adapter 2 DNS1:192.168.24.212
+[2025-11-24 17:34:46.931] luat:D(3340):netdrv:exec wifi_netif_notify_sta_got_ip
+[2025-11-24 17:34:46.931] luat:D(3341):net:network ready 2, setup dns server
+[2025-11-24 17:34:46.931] luat:D(3341):ulwip:IP_READY 2 192.168.24.119
+[2025-11-24 17:34:46.931] luat:U(3343):I/user.netdrv_wifi.ip_ready_func IP_READY {"gw":"192.168.24.212","rssi":-44,"bssid":"DA04CBA995AB"}
+[2025-11-24 17:34:46.931] luat:D(3346):lfs:init ok
+[2025-11-24 17:34:46.931] luat:U(3352):I/user.exmtn 读取索引 1
+[2025-11-24 17:34:46.931] luat:U(3353):I/user.exmtn 读取块数配置 1
+[2025-11-24 17:34:46.931] luat:U(3353):I/user.exmtn 读取写入方式配置 1
+[2025-11-24 17:34:46.931] luat:U(3353):I/user.exmtn 配置变化 false
+[2025-11-24 17:34:46.931] luat:U(3354):I/user.exmtn 配置未变化,文件存在,继续写入
+[2025-11-24 17:34:46.931] luat:U(3364):I/user.exmtn 初始化成功: 每个文件 4.00 KB (1 块 × 4096 字节), 总空间 16.00 KB (4 个文件)
+[2025-11-24 17:34:46.931] luat:U(3364):I/user.[excloud]运维日志初始化成功
+[2025-11-24 17:34:46.931] luat:U(3364):I/user.[excloud]excloud.setup 初始化成功 设备ID: C8C2C68C5D3E
+[2025-11-24 17:34:46.931] luat:U(3364):I/user.excloud初始化成功
+[2025-11-24 17:34:46.931] luat:U(3364):I/user.[excloud]首次连接,获取服务器信息...
+[2025-11-24 17:34:46.931] luat:U(3365):I/user.[excloud]excloud.getip 类型: 3 key: VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi-C8C2C68C5D3E
+[2025-11-24 17:34:46.931] luat:D(3367):socket:connect to gps.openluat.com,443
+[2025-11-24 17:34:46.931] luat:D(3367):DNS:gps.openluat.com state 0 id 1 ipv6 0 use dns server0, try 0
+[2025-11-24 17:34:46.931] luat:D(3367):net:adatper 2 dns server 192.168.24.212
+[2025-11-24 17:34:46.931] luat:D(3368):net:dns udp sendto 192.168.24.212:53 from 192.168.24.119
+[2025-11-24 17:34:46.931] luat:D(3370):wlan:event_module 2 event_id 0
+[2025-11-24 17:34:46.931] luat:I(3378):DNS:dns all done ,now stop
+[2025-11-24 17:34:46.931] luat:D(3378):net:connect 1.94.5.143:443 TCP
+[2025-11-24 17:34:47.235] luat:U(3670):I/user.mtn_test 4
+[2025-11-24 17:34:47.290] luat:U(3720):I/user.httpplus 等待服务器完成响应
+[2025-11-24 17:34:47.696] luat:U(4123):I/user.httpplus 等待服务器完成响应
+[2025-11-24 17:34:47.696] luat:U(4125):I/user.httpplus 服务器已完成响应,开始解析响应
+[2025-11-24 17:34:47.715] luat:U(4140):I/user.[excloud]excloud.getip响应 HTTP Code: 200 Body: luat:U(4140):{"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108},"imginfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/image","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}},"audinfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/audio","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}},"mtninfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/file","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}}}luat:U(4140):
+[2025-11-24 17:34:47.715] luat:U(4140):I/user.[excloud]excloud.getip响应 HTTP Code: 200 Body: luat:U(4140):{"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108},"imginfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/image","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}},"audinfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/audio","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}},"mtninfo":{"url":"https://gps.openluat.com/iot/aircloud/upload/file","data_key":"f","data_param":{"key":"Wx3EpbWaMnFSnfLnSpLACxURCUiX66HMAdD8VW","tip":""}}}luat:U(4141):
+[2025-11-24 17:34:47.715] luat:U(4142):I/user.[excloud]获取到TCP/UDP连接信息 host: 124.71.128.165 port: 9108
+[2025-11-24 17:34:47.715] luat:U(4142):I/user.[excloud]获取到图片上传信息
+[2025-11-24 17:34:47.715] luat:U(4143):I/user.[excloud]获取到音频上传信息
+[2025-11-24 17:34:47.715] luat:U(4143):I/user.[excloud]获取到运维日志上传信息
+[2025-11-24 17:34:47.715] luat:U(4143):I/user.[excloud]excloud.getip 更新配置: 124.71.128.165 9108
+[2025-11-24 17:34:47.715] luat:U(4143):I/user.[excloud]excloud.getip 成功: true 结果: {"ipv4":"124.71.128.165","port":9108}
+[2025-11-24 17:34:47.715] luat:U(4144):I/user.[excloud]服务器信息获取成功 host: 124.71.128.165 port: 9108 transport: tcp
+[2025-11-24 17:34:47.715] luat:U(4144):I/user.[excloud]创建TCP连接
+[2025-11-24 17:34:47.715] luat:D(4145):socket:connect to 124.71.128.165,9108
+[2025-11-24 17:34:47.715] luat:U(4145):network_socket_connect 1578:network 0 local port auto select 51282
+[2025-11-24 17:34:47.715] luat:D(4145):net:connect 124.71.128.165:9108 TCP
+[2025-11-24 17:34:47.715] luat:U(4146):I/user.[excloud]TCP连接结果 true false
+[2025-11-24 17:34:47.715] luat:U(4147):I/user.[excloud]excloud service started
+[2025-11-24 17:34:47.715] luat:U(4147):I/user.excloud服务已开启
+[2025-11-24 17:34:47.715] luat:U(4147):I/user.[excloud]excloud 自动心跳已启动,间隔 300 秒
+[2025-11-24 17:34:47.715] luat:U(4148):I/user.自动心跳已启动
+[2025-11-24 17:34:47.849] luat:U(4197):network_default_socket_callback 1103:before process socket 1,event:0xf2000009(连接成功),state:3(正在连接),wait:2(等待连接完成)
+[2025-11-24 17:34:47.849] luat:U(4197):network_default_socket_callback 1107:after process socket 1,state:5(在线),wait:0(无等待)
+[2025-11-24 17:34:47.849] luat:U(4199):I/user.[excloud]socket cb userdata: 60C58268 33554449 0
+[2025-11-24 17:34:47.849] luat:U(4200):I/user.[excloud]socket TCP连接成功
+[2025-11-24 17:34:47.849] luat:U(4200):I/user.用户回调函数 connect_result {"success":true}
+[2025-11-24 17:34:47.849] luat:U(4200):I/user.连接成功
+[2025-11-24 17:34:47.849] luat:U(4201):I/user.[excloud]构建发送数据 16 3 VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi-C8C2C68C5D3E-54540D4935 
+[2025-11-24 17:34:47.849] luat:U(4201):I/user.[excloud]tlv发送数据长度4 60
+[2025-11-24 17:34:47.849] luat:U(4202):                   I/user.[excloud]构建消息头 luat:U(4203):I/user.[excloud]发送消息长度 16 60 76 0200C8C2C68C5D3E0002003C0000001130100038566D68744F62383145675A617536597975755A4A7A7746366F554E47436258692D4338433243363843354433452D35343534304434393335 152
+[2025-11-24 17:34:47.849] luat:U(4205):I/user.用户回调函数 send_result {"sequence_num":1,"success":true,"error_msg":"Send successful"}
+[2025-11-24 17:34:47.849] luat:U(4205):I/user.发送成功,流水号: 1
+[2025-11-24 17:34:47.849] luat:U(4211):I/user.[excloud]数据发送成功 76 字节
+```
+
+

BIN
module/Air8101/demo/aircloud/test.jpg


+ 72 - 31
script/libs/excloud.lua

@@ -86,8 +86,8 @@ local config = {
     virtual_serial_num = 0,     -- 序列号(0-999)
 
     -- 运维日志配置
-    mtn_log_enabled = false,                -- 是否启用运维日志
-    aircloud_mtn_log_enabled = false,       -- 是否启用aircloud运维日志:true-开启,false-关闭;开启后设备认证/重连等关键事件会自动记录到运维日志文件,便于云端统一收集分析
+    mtn_log_enabled = false,               -- 是否启用运维日志
+    aircloud_mtn_log_enabled = false,      -- 是否启用aircloud运维日志:true-开启,false-关闭;开启后设备认证/重连等关键事件会自动记录到运维日志文件,便于云端统一收集分析
     mtn_log_blocks = 1,                    -- 每个文件的块数
     mtn_log_write_way = exmtn.CACHE_WRITE, -- 写入方式
 
@@ -780,7 +780,7 @@ local function upload_mtn_log_files(log_files)
 
                 -- 记录上传失败的运维日志
                 if config.aircloud_mtn_log_enabled then
-                    exmtn.log("info", "aircloud","mtn_upload_error", "文件上传失败", "file", log_file.name, "error", err_msg)
+                    exmtn.log("info", "aircloud", "mtn_upload_error", "文件上传失败", "file", log_file.name, "error", err_msg)
                 end
             end
 
@@ -806,7 +806,8 @@ local function upload_mtn_log_files(log_files)
 
         -- 记录上传完成日志
         if config.aircloud_mtn_log_enabled then
-            exmtn.log("info", "aircloud","mtn_upload", "运维日志上传完成", "success", success_count, "failed", failed_count, "total", total_files)
+            exmtn.log("info", "aircloud", "mtn_upload", "运维日志上传完成", "success", success_count, "failed", failed_count,
+                "total", total_files)
         end
         -- 通知上传完成
         if callback_func then
@@ -825,7 +826,7 @@ local function handle_mtn_log_upload_request()
     local latest_index = total_files > 0 and log_files[#log_files].index or 0
 
     if config.aircloud_mtn_log_enabled then
-        exmtn.log("info", "aircloud","cloud_cmd", "收到运维日志上传请求", "file_count", total_files)
+        exmtn.log("info", "aircloud", "cloud_cmd", "收到运维日志上传请求", "file_count", total_files)
     end
 
     log.info("开始处理运维日志上传请求", "文件总数:", total_files, "最新序号:", latest_index)
@@ -922,11 +923,6 @@ function excloud.getip(getip_type)
     local key = config.auth_key .. "-" .. config.device_id
     log.info("[excloud]excloud.getip", "类型:", getip_type, "key:", key)
 
-    -- 构建请求数据
-    local request_data = {
-        key = key,
-        type = getip_type
-    }
     -- 执行HTTP请求
     local code, response = httpplus.request(
         {
@@ -1059,7 +1055,7 @@ function excloud.getip(getip_type)
         log.warn("[excloud]未获取到有效的连接信息,将使用原有配置")
     end
 
-    return true, response_json
+    return true, config.current_conninfo
 end
 
 -- 带重试的getip请求
@@ -1070,7 +1066,7 @@ function excloud.getip_with_retry(getip_type)
 
     while retry_count < max_retry do
         success, result = excloud.getip(getip_type)
-        if success and result.conninfo then
+        if success and result then
             log.info("[excloud]excloud.getip", "成功:", success, "结果:", json.encode(result))
             config.getip_retry_count = 0
             return true, result
@@ -1334,11 +1330,11 @@ excloud.mtn_log("info", "message", 123)
 excloud.mtn_log("warn", "message", 456)
 excloud.mtn_log("error", "message", 789)
 ]]
-function excloud.mtn_log(level,tag, ...)
+function excloud.mtn_log(level, tag, ...)
     if not config.mtn_log_enabled then
         return false, "运维日志功能已禁用" -- 禁用时返回失败
     end
-    exmtn.log(level,tag, ...)
+    exmtn.log(level, tag, ...)
     return true
 end
 
@@ -1380,7 +1376,9 @@ local function schedule_reconnect()
     -- 检查是否达到最大重连次数
     if reconnect_count >= config.max_reconnect then
         log.info("[excloud]到达最大重连次数 " .. reconnect_count .. "/" .. config.max_reconnect)
-
+        -- 执行紧急内存清理
+        collectgarbage("collect")
+        pending_messages = {}
         -- 根据use_getip决定是否重新获取服务器信息
         if config.use_getip then
             log.info("[excloud]TCP连接多次失败,重新获取服务器信息...")
@@ -1404,6 +1402,8 @@ local function schedule_reconnect()
                 -- 使用新的服务器信息重新连接
                 sys.timerStart(function()
                     log.info("[excloud]使用新服务器信息重新连接")
+                    excloud.close() -- 确保完全关闭
+                    sys.wait(200)
                     excloud.open()
                 end, config.reconnect_interval * 1000)
             else
@@ -1446,7 +1446,24 @@ local function schedule_reconnect()
             log.info("[excloud]服务已关闭,取消重连")
             return
         end
+        -- 先执行内存清理
+        collectgarbage("collect")
+        -- 如果连接对象存在但连接已断开,先清理
+        if connection and not is_connected then
+            log.info("[excloud]清理残留的连接对象")
+            if config.transport == "tcp" then
+                socket.close(connection)
+                socket.release(connection)
+            elseif config.transport == "mqtt" then
+                connection:disconnect()
+                connection:close()
+            end
+            connection = nil
+        end
 
+        -- 重置连接状态但保持服务开启状态
+        is_connected = false
+        is_authenticated = false
         -- 执行重连
         local success, err = excloud.open()
         if not success then
@@ -1470,11 +1487,11 @@ local function tcp_socket_callback(netc, event, param)
     -- 记录连接状态变化的运维日志
     if config.aircloud_mtn_log_enabled then
         if event == socket.LINK then
-            exmtn.log("info", "aircloud","net_conn", "网络连接成功")
+            exmtn.log("info", "aircloud", "net_conn", "网络连接成功")
         elseif event == socket.ON_LINE then
-            exmtn.log("info", "aircloud","net_conn", "TCP连接成功", "host", config.host, "port", config.port)
+            exmtn.log("info", "aircloud", "net_conn", "TCP连接成功", "host", config.host, "port", config.port)
         elseif event == socket.CLOSED then
-            exmtn.log("info", "aircloud","net_conn", "TCP连接断开", "param", param)
+            exmtn.log("info", "aircloud", "net_conn", "TCP连接断开", "param", param)
         end
     end
 
@@ -1528,6 +1545,8 @@ local function tcp_socket_callback(netc, event, param)
         log.info("[excloud]socket", "发送完成")
     elseif event == socket.CLOSED then
         -- 连接错误或关闭
+        socket.release(connection)
+        connection = nil
         log.info("[excloud]socket", "主动断开链接")
     end
 end
@@ -1544,11 +1563,11 @@ local function mqtt_client_event_cbfunc(connected, event, data, payload, metas)
     -- 记录MQTT状态变化的运维日志
     if config.aircloud_mtn_log_enabled then
         if event == "conack" then
-            exmtn.log("info", "aircloud","mqtt_conn", "MQTT连接成功", "host", config.host)
+            exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接成功", "host", config.host)
         elseif event == "disconnect" then
-            exmtn.log("info", "aircloud","mqtt_conn", "MQTT连接断开")
+            exmtn.log("info", "aircloud", "mqtt_conn", "MQTT连接断开")
         elseif event == "error" then
-            exmtn.log("info", "aircloud","mqtt_error", "MQTT错误", "type", data, "code", payload)
+            exmtn.log("info", "aircloud", "mqtt_error", "MQTT错误", "type", data, "code", payload)
         end
     end
 
@@ -1740,11 +1759,17 @@ end
 
 -- 开启excloud服务
 function excloud.open()
+    -- 如果之前连接异常断开,但状态未重置,先清理
+    if is_open and not is_connected then
+        log.warn("[excloud]检测到状态不一致,先清理残留状态")
+        excloud.close()
+        sys.wait(100) -- 短暂等待确保资源释放
+    end
     -- 检查是否已打开
-    if is_open then
-        return false, "excloud is already open"
+    if is_open and is_connected then
+        return false, "excloud is already open and connected"
     end
-
+    reconnect_count = 0
     -- 判断是否初始化
     if not device_id_binary then
         return false, "excloud 没有初始化,请先调用setup"
@@ -1998,11 +2023,12 @@ function excloud.open()
     end
 
     is_open = true
-    reconnect_count = 0
+
 
     -- 记录服务启动日志
     if config.aircloud_mtn_log_enabled then
-        exmtn.log("info", "aircloud","system", "excloud服务启动", "transport", config.transport, "host", config.host, "port", config.port)
+        exmtn.log("info", "aircloud", "system", "excloud服务启动", "transport", config.transport, "host", config.host, "port",
+            config.port)
     end
 
     log.info("[excloud]excloud service started")
@@ -2061,16 +2087,24 @@ function excloud.send(data, need_reply, is_auth_msg)
 
     -- 保存当前序列号用于回调
     local current_sequence = sequence_num
-    local success
     -- 构建消息体
     local message_body = ""
+    local parts = {}
     for _, item in ipairs(data) do
         log.info("[excloud]构建发送数据", item.field_meaning, item.data_type, item.value, message_body)
         local success, tlv = build_tlv(item.field_meaning, item.data_type, item.value)
         if not success then
             return false, "excloud.send data is failed"
         end
-        message_body = message_body .. tlv
+        table.insert(parts, tlv)
+        -- message_body = message_body .. tlv
+    end
+    if #parts > 0 then
+        message_body = table.concat(parts)
+        parts = {}
+    else
+        log.warn("[excloud]没有有效的TLV数据可发送")
+        -- return false, "No valid TLV data to send"
     end
 
     -- 检查消息长度
@@ -2143,7 +2177,7 @@ function excloud.send(data, need_reply, is_auth_msg)
             sequence_num = current_sequence
         })
     end
-
+    collectgarbage("collect")
     if success then
         log.info("[excloud]数据发送成功", #full_message, "字节")
         return true
@@ -2182,10 +2216,16 @@ function excloud.close()
         end
         connection = nil
     end
-
+    -- 释放缓冲区
+    if rxbuff then
+        rxbuff = nil
+    end
+    -- 清空队列
+    pending_messages = {}
+    callback_func = nil
     -- 记录服务关闭日志
     if config.aircloud_mtn_log_enabled then
-        exmtn.log("info", "aircloud","system", "excloud服务关闭")
+        exmtn.log("info", "aircloud", "system", "excloud服务关闭")
     end
 
     -- 重置状态
@@ -2196,6 +2236,7 @@ function excloud.close()
     rxbuff = nil
     reconnect_count = 0
     is_heartbeat_running = false
+    collectgarbage("collect")
     log.info("[excloud]excloud service stopped")
     return true
 end