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

update:Air780EHM/EHV/EGH Websocket demo

13917187172 6 месяцев назад
Родитель
Сommit
b3502dbc23

+ 0 - 122
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/WebSocket-UART透传/main.lua

@@ -1,122 +0,0 @@
-
-PROJECT = "airtun"
-VERSION = "1.0.0"
-
--- sys库是标配
-_G.sys = require("sys")
--- _G.sysplus = require("sysplus")
-
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
-if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
-end
-----------------------------------------
--- 报错信息自动上报到平台,默认是iot.openluat.com
--- 支持自定义, 详细配置请查阅API手册
--- 开启后会上报开机原因, 这需要消耗流量,请留意
-if errDump then
-    errDump.config(true, 600)
-end
-----------------------------------------
-local tx_buff = zbuff.create(1024)      -- 发送至WebSocket服务器的数据
-local uart_rx_buff = zbuff.create(1024)     -- 串口接收到的数据
-local uartid = 1 -- 根据实际设备选取不同的uartid
-Sbuf = 0
-
---初始化
-uart.setup(
-    uartid,--串口id
-    115200,--波特率
-    8,--数据位
-    1--停止位
-)
-
-local wsc = nil
-
-sys.taskInit(function()
-
-    sys.waitUntil("IP_READY")                -- 等待联网成功
-
-    -- 这是个测试服务, 当发送的是json,且action=echo,就会回显所发送的内容
-    -- 加密TCP链接 wss 表示加密
-    wsc = websocket.create(nil, "wss://echo.airtun.air32.cn/ws/echo")
-    -- 这是另外一个测试服务, 能响应websocket的二进制帧
-    -- wsc = websocket.create(nil, "ws://echo.airtun.air32.cn/ws/echo2")
-    -- 以上两个测试服务是Java写的, 源码在 https://gitee.com/openLuat/luatos-airtun/tree/master/server/src/main/java/com/luatos/airtun/ws
-
-    if wsc.headers then
-        wsc:headers({Auth="Basic ABCDEGG"})
-    end
-    wsc:autoreconn(true, 3000) -- 自动重连机制
-    wsc:on(function(wsc, event, data, fin, optcode)
-        --[[
-            event的值有:
-            conack 连接服务器成功,已经收到websocket协议头部信息,通信已建立
-            recv   收到服务器下发的信息, data, payload 不为nil
-            sent   send函数发送的消息,服务器在TCP协议层已确认收到
-            disconnect 服务器连接已断开
-
-            其中 sent/disconnect 事件在 2023.04.01 新增
-        ]]
-        -- data 当事件为recv是有接收到的数据
-        -- fin 是否为最后一个数据包, 0代表还有数据, 1代表是最后一个数据包
-        -- optcode, 0 - 中间数据包, 1 - 文本数据包, 2 - 二进制数据包
-        -- 因为lua并不区分文本和二进制数据, 所以optcode通常可以无视
-        -- 若数据不多, 小于1400字节, 那么fid通常也是1, 同样可以忽略
-        log.info("wsc", event, data, fin, optcode)
-        -- 显示二进制数据
-        -- log.info("wsc", event, data and data:toHex() or "", fin, optcode)
-        if event == "conack" then -- 连接websocket服务后, 会有这个事件
-            log.info("WebSocket connect succeed!")
-            sys.publish("wsc_conack")
-        end
-    end)
-    wsc:connect()
-    sys.waitUntil("wsc_conack")
-    while true do
-        uart.on(uartid, "receive", function(id, len)
-            while true do
-                local len = uart.rx(id, uart_rx_buff)   -- 接收串口收到的数据,并赋值到uart_rx_buff
-                if len <= 0 then    -- 接收到的字节长度为0 则退出
-                    break
-                else
-                    uart_rx_buff:seek(0)
-                    uart_rx_buff_data = uart_rx_buff:read(len)
-                    Sbuf = len
-                    log.info("UART接收的数据包",uart_rx_buff_data)
-                    break
-                end
-            end
-        end)
-
-        if Sbuf > 0 then
-            log.info("发送到服务器数据,长度",Sbuf)
-            log.info("UART发送到服务器的数据包 ",uart_rx_buff_data)
-            log.info("UART发送到服务器的数据包类型 ",type(uart_rx_buff_data))
-            if uart_rx_buff_data == '"echo"' then               -- 连接收到串口发送的"echo" ,会进行数据发送
-                log.info("UART透传成功 进行数据发送")
-                wsc:send(json.encode({action="echo", msg=os.date()})) ---发送数据
-            end
-        end
-        Sbuf = 0
-        uart_rx_buff:del()                      -- 清除串口buff的数据长度
-        sys.wait(1000) 
-    end
-
-    wsc:close()
-    wsc = nil
-
-end)
-
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 83
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/WebSocket加密通讯/main.lua

@@ -1,83 +0,0 @@
-
-PROJECT = "airtun"
-VERSION = "1.0.0"
-
--- sys库是标配
-_G.sys = require("sys")
--- _G.sysplus = require("sysplus")
-
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
-----------------------------------------
--- 报错信息自动上报到平台,默认是iot.openluat.com
--- 支持自定义, 详细配置请查阅API手册
--- 开启后会上报开机原因, 这需要消耗流量,请留意
-if errDump then
-    errDump.config(true, 600)
-end
-----------------------------------------
-
-local wsc = nil
-
-sys.taskInit(function()
-
-    sys.waitUntil("IP_READY")                -- 等待联网成功
-
-    -- 这是个测试服务, 当发送的是json,且action=echo,就会回显所发送的内容
-    -- 加密TCP链接 wss 表示加密
-    wsc = websocket.create(nil, "wss://echo.airtun.air32.cn/ws/echo")
-    -- 这是另外一个测试服务, 能响应websocket的二进制帧
-    -- wsc = websocket.create(nil, "ws://echo.airtun.air32.cn/ws/echo2")
-    -- 以上两个测试服务是Java写的, 源码在 https://gitee.com/openLuat/luatos-airtun/tree/master/server/src/main/java/com/luatos/airtun/ws
-
-    if wsc.headers then
-        wsc:headers({Auth="Basic ABCDEGG"})
-    end
-    wsc:autoreconn(true, 3000) -- 自动重连机制
-    wsc:on(function(wsc, event, data, fin, optcode)
-        --[[
-            event的值有:
-            conack 连接服务器成功,已经收到websocket协议头部信息,通信已建立
-            recv   收到服务器下发的信息, data, payload 不为nil
-            sent   send函数发送的消息,服务器在TCP协议层已确认收到
-            disconnect 服务器连接已断开
-
-            其中 sent/disconnect 事件在 2023.04.01 新增
-        ]]
-        -- data 当事件为recv是有接收到的数据
-        -- fin 是否为最后一个数据包, 0代表还有数据, 1代表是最后一个数据包
-        -- optcode, 0 - 中间数据包, 1 - 文本数据包, 2 - 二进制数据包
-        -- 因为lua并不区分文本和二进制数据, 所以optcode通常可以无视
-        -- 若数据不多, 小于1400字节, 那么fid通常也是1, 同样可以忽略
-        log.info("wsc", event, data, fin, optcode)
-        -- 显示二进制数据
-        -- log.info("wsc", event, data and data:toHex() or "", fin, optcode)
-        if event == "conack" then -- 连接websocket服务后, 会有这个事件
-            log.info("WebSocket connect succeed!")
-            sys.publish("wsc_conack")
-        end
-    end)
-    wsc:connect()
-    -- 等待conack是可选的
-    sys.waitUntil("wsc_conack")
-    --local stat = wsc:ready()
-    -- 定期发业务ping也是可选的, 但为了保存连接, 也为了继续持有wsc对象, 这里周期性发数据
-    while true do
-        wsc:send((json.encode({action="echo", msg=os.date()})))
-        sys.wait(15000)
-        -- 发送二进制帧, 2023.06.21 之后编译的固件支持
-        -- wsc:send(string.char(0xA5, 0x5A, 0xAA, 0xF2), 1, 1)
-    end
-    wsc:close()
-    wsc = nil
-end)
-
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 90 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/main.lua

@@ -0,0 +1,90 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本demo演示的核心功能为:
+1、创建一个WebSocket连接,连接WebSocket server;
+2、WebSocket连接出现异常后,自动重连;
+3、WebSocket连接,client按照以下几种逻辑发送数据给server
+- 串口应用功能模块uart_app.lua,通过uart1接收到串口数据,将串口数据增加send from uart: 前缀后发送给server;
+- 定时器应用功能模块timer_app.lua,定时产生数据,将数据增加send from timer:前缀后发送给server;
+4、WebSocket连接,client收到server数据后,将数据增加recv from websocket server: 前缀后,通过uart1发送出去;
+5、启动一个网络业务逻辑看门狗task,用来监控网络环境,如果连续长时间工作不正常,重启整个软件系统;
+6、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+   (1) netdrv_4g:4G网卡
+   (2) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (3) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+  
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "WEBSOCKET_LONG_CONNECTION"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+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)
+
+-- 加载网络环境检测看门狗功能模块
+require "network_watchdog"
+
+-- 加载网络驱动设备功能模块
+require "netdrv_device"
+
+-- 加载串口应用功能模块
+require "uart_app"
+
+-- 加载定时器应用功能模块
+require "timer_app"
+
+-- 加载WebSocket client主应用功能模块
+require "websocket_main"
+
+-- 用户代码已结束---------------------------------------------
+sys.run()

+ 36 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/netdrv/netdrv_4g.lua

@@ -0,0 +1,36 @@
+--[[
+@module  netdrv_4g
+@summary “4G网卡”驱动模块
+@version 1.0
+@date    2025.07.01
+@author  马梦阳
+@usage
+本文件为4G网卡驱动模块,核心业务逻辑为:
+1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_GP then
+        log.info("netdrv_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_GP))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_GP then
+        log.warn("netdrv_4g.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+-- 设置默认网卡为socket.LWIP_GP
+-- 在Air780EXX上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
+-- 在单4G网卡使用场景下,下面这一行代码加不加都没有影响,为了和其他网卡驱动模块的代码风格保持一致,所以加上了
+socket.dft(socket.LWIP_GP)

+ 100 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,100 @@
+--[[
+@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、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+Air780EXX核心板和AirETH_1000配件板的硬件接线方式为:
+核心板通过TYPE-C USB口供电(TYPE-C USB口旁边的ON/OFF拨动开关拨到ON一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的5V管脚进行5V供电;
+| Air780EXX核心板  |  AirETH_1000配件板 |
+| --------------- | ----------------- |
+| 3V3             | 3.3v              |
+| gnd             | gnd               |
+| 86/SPI0CLK      | SCK               |
+| 83/SPI0CS       | CSS               |
+| 84/SPI0MISO     | SDO               |
+| 85/SPI0MOSI     | SDI               |
+| 107/GPIO21      | INT               |
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是Air780EXX核心板
+-- 核心板的VDD 3V3管脚对AirETH_1000配件板进行供电
+-- 3V3管脚是作为LDO 3.3V输出,供测试用的,仅在使用DCDC供电时有输出,默认打开,无需控制
+
+
+-- 这个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_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 0, 片选 GPIO8
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=0, cs=8})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 94 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/netdrv/netdrv_multiple.lua

@@ -0,0 +1,94 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+通过SPI外挂CH390H芯片的以太网卡:
+Air780EXX核心板和AirETH_1000配件板的硬件接线方式为:
+核心板通过TYPE-C USB口供电(TYPE-C USB口旁边的ON/OFF拨动开关拨到ON一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的5V管脚进行5V供电;
+| Air780EXX核心板  |  AirETH_1000配件板 |
+| --------------- | ----------------- |
+| 3V3             | 3.3v              |
+| gnd             | gnd               |
+| 86/SPI0CLK      | SCK               |
+| 83/SPI0CS       | CSS               |
+| 84/SPI0MISO     | SDO               |
+| 85/SPI0MOSI     | SDI               |
+| 107/GPIO21      | INT               |
+
+本文件没有对外接口,直接在其他功能模块中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(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air780EXX核心板验证
+            {
+                ETHERNET = {
+                    -- 本demo测试使用的是Air780EXX核心板
+                    -- 核心板的VDD 3V3管脚对AirETH_1000配件板进行供电
+                    -- 3V3管脚是作为LDO 3.3V输出,供测试用的,仅在使用DCDC供电时有输出,默认打开,无需控制
+                    -- 供电使能GPIO
+                    -- pwrpin = 20,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=0, cs=8}
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 29 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/netdrv_device.lua

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

+ 57 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/network_watchdog.lua

@@ -0,0 +1,57 @@
+--[[
+@module  network_watchdog
+@summary 网络环境检测看门狗功能模块 
+@version 1.0
+@date    2025.07.23
+@author  朱天华
+@usage
+本文件为网络环境检测看门狗功能模块,监控网络环境是否工作正常(设备和服务器双向通信正常,或者至少单向通信正常),核心业务逻辑为:
+1、启动一个网络环境检测看门狗task,等待其他WebSocket网络应用功能模块来喂狗,如果喂狗超时,则控制软件重启;
+2、如何确定"喂狗超时时间",一般来说,有以下几个原则;
+   (1) 先确定一个最小基准值T1,2分钟或者5分钟或者10分钟,这个取值取决于具体项目需求,但是不能太短,因为开机后,在网络环境不太好的地方,网络初始化可能需要比较长的时间,一般推荐这个值不能小于2分钟;
+   (2) 再确定一个和产品业务逻辑有关的一个值T2,这个值和产品的应用业务逻辑息息相关,假设你的产品业务中:
+       <1> 服务器会定时下发数据给设备,例如设备连接上业务服务器之后,每隔3分钟,设备都会给服务器发送一次心跳,然后服务器都会立即回复一个心跳应答包;
+           这种情况下,可以取3分钟的大于等于1的倍数(例如1倍,1.5倍,2倍等等)+一段时间(例如10秒钟,如果前面是1倍,则此处必须加一段时间,给网络数据传输过程留够充足的时间);
+       <2> 如果服务器不会定时下发数据给设备,但是WebSocket使用的是长连接,并且设备会定时发送数据给服务器,例如设备连接上业务服务器之后,每隔2分钟,设备都会给服务器发送一次心跳;
+           这种情况下,可以取2分钟的大于等于1的倍数(例如1倍,1.5倍,2倍等等)+一段时间(例如10秒钟,如果前面是1倍,则此处必须加一段时间,给网络数据传输过程留够充足的时间);       
+       <3> 如果服务器既不会定时或者至少一段时间下发应用数据给设备,设备也不会定时或者至少一段时间上传应用数据到服务器;
+           这种情况下,一般来说也不是长连接应用,一般来说也不需要网络业务逻辑看门狗,遇到这种情况再具体问题具体分析;
+    (3) 取T1和T2的最大值,就是"喂狗超时时间"
+3、其他WebSocket网络业务功能模块的喂狗时机,和上面2.2的描述相对应,一般来说,可以在以下几种时间点执行喂狗动作:
+   (1) 设备收到服务器下发的数据时
+   (2) WebSocket连接下,设备成功发送数据到服务器时
+   (3) WebSocket连接成功时(不到迫不得已,这种情况下不要喂狗,如果喂狗,可能会影响以上两点的判断;
+                因为长连接的收发数据失败会导致一直重连,重连成功喂狗就会掩盖收发数据异常,除非收发数据完全无规律,才可能在WebSocket连接成功时喂狗)
+4、最重要的一点是:以上所说的原则,仅仅是建议,要根据自己的实际项目业务逻辑以及自己的需求最终确定看门狗方案
+
+5、具体到本demo
+   (1) 产品业务逻辑为:
+       <1> 创建了一个WebSocket连接,设备会定时发送数据到服务器,服务器何时下发数据给设备不确定;
+   (2) 确定喂狗超时时间:
+       <1> 本demo支持单WIFI、单以太网、单4G网络连接外网,网络环境准备就绪预留2分钟的时间已经足够,所以最小基准值T1取值2分钟;
+       <2> 本demo中存在1路WebSocket连接,设备会定时发送数据给服务器,定时发送的时间间隔为5秒,在网络环境波动的时候,数据发送延时会比较大;
+           在这个demo中,我能接受的延时发送时长是1分钟,能接受连续3次延时发送时长的失败,所以,T2取值3分钟;
+       <3> 取T1 2分钟和T2 3分钟的最大值,最终的喂狗超时时间就是3分钟;
+   (3) 确定喂狗时机:
+       <1> WebSocket连接中,收到服务器的下发数据时;       
+       <2> WebSocket连接中,成功发送数据给服务器时;
+
+本文件没有对外接口,直接在main.lua中require "network_watchdog"就可以加载运行;
+外部功能模块喂狗时,直接调用sys.publish("FEED_NETWORK_WATCHDOG")
+]]
+
+-- 网络环境检测看门狗task处理函数
+local function network_watchdog_task_func()
+    while true do
+        --如果等待180秒没有等到"FEED_NETWORK_WATCHDOG"消息,则看门狗超时
+        if not sys.waitUntil("FEED_NETWORK_WATCHDOG", 180000) then            
+            log.error("network_watchdog_task_func timeout")
+            -- 等待3秒钟,然后软件重启
+            sys.wait(3000)
+            rtos.reboot()
+        end
+    end
+end
+
+--创建并且启动一个task
+sys.taskInit(network_watchdog_task_func)

+ 149 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/readme.md

@@ -0,0 +1,149 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的三种网卡(单4g网卡,单spi以太网卡,多网卡)中的任何一种网卡;
+
+3、websocket_main.lua:WebSocket client连接以及数据收发处理主逻辑;
+
+4、websocket_receiver.lua:WebSocket client数据发送处理模块;
+
+5、websocket_sender.lua:WebSocket client数据接收处理模块;
+
+6、network_watchdog.lua:网络环境检测看门狗;
+
+7、timer_app.lua:通知websocket client定时发送数据到服务器;
+
+8、uart_app.lua:在websocket client和uart外设之间透传数据;
+
+## 系统消息介绍
+
+1、"IP_READY":某种网卡已经获取到ip信息,仅仅获取到了ip信息,能否和外网连通还不确认;
+
+2、"IP_LOSE":某种网卡已经掉网;
+
+
+## 用户消息介绍
+
+1、"RECV_DATA_FROM_SERVER":socket client收到服务器下发的数据后,通过此消息发布出去,给其他应用模块处理;
+
+2、"SEND_DATA_REQ":其他应用模块发布此消息,通知WebSocket 客户端发送数据给服务器;
+
+3、"FEED_NETWORK_WATCHDOG":网络环境检测看门狗的喂狗消息,在需要喂狗的地方发布此消息;
+
+
+## 演示功能概述
+
+1、创建WebSocket连接,详情如下:
+
+   注意:代码中的WebSocket服务器地址和端口会不定期重启或维护,仅能用作测试用途,不可商用,说不定哪一天就关闭了。用户开发项目时,需要替换为自己的商用服务器地址和端口。
+
+   创建一个WebSocket client,连接WebSocket server;
+
+   支持wss加密连接;
+
+2、WebSocket连接出现异常后,自动重连;
+
+3、WebSocket client按照以下几种逻辑发送数据给server:
+
+   串口应用功能模块uart_app.lua,通过uart1接收到串口数据,将串口数据转发给server;
+
+   定时器应用功能模块timer_app.lua,定时产生数据,将数据发送给server;
+
+   特殊命令处理:当收到"echo"命令时,会发送包含时间信息的JSON数据;
+
+4、WebSocket client收到server数据后,将数据增加"收到WebSocket服务器数据: "前缀后,通过uart1发送出去;
+
+5、启动一个网络业务逻辑看门狗task,用来监控网络环境,如果连续长时间工作不正常,重启整个软件系统;
+
+6、netdrv_device:配置连接外网使用的网卡,目前支持以下三种选择(三选一)
+
+   (1) netdrv_4g:4G网卡
+
+   (2) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+
+   (3) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV.png)
+
+1、Air780EXX 核心板一块
+- sim卡插入开发板的sim卡槽
+
+2、TYPE-C USB 数据线一根
+
+3、USB 转串口数据线一根
+
+4、Air780EXX 核心板和数据线的硬件接线方式为
+- Air780EXX 核心板通过 TYPE-C USB 口供电;
+- 如果测试发现软件频繁重启,重启原因值为:poweron reason 0,可能是供电不足,此时再通过直流稳压电源对核心板的 vbat 管脚进行 4V 供电,或者 5V 管脚进行 5V 供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+- USB 转串口数据线,一般来说,白线连接核心板的 18/U1TXD,绿线连接核心板的 17/U1RXD,黑线连接核心板的 gnd,另外一端连接电脑 USB 口;
+
+5、可选AirPHY_1000 配件板一块,Air780EXX 核心板和 AirPHY_1000 配件板的硬件接线方式为:
+
+| Air780EXX核心板  |  AirETH_1000配件板 |
+| --------------- | ----------------- |
+| 3V3             | 3.3v              |
+| gnd             | gnd               |
+| 86/SPI0CLK      | SCK               |
+| 83/SPI0CS       | CSS               |
+| 84/SPI0MISO     | SDO               |
+| 85/SPI0MOSI     | SDI               |
+| 107/GPIO21      | INT               |
+
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具 [https://docs.openluat.com/air780epm/common/Luatools/]
+
+2、Air780EHM V2012 版本固件、Air780EHV V2012 版本固件、Air780EGH V2012 版本固件(理论上,2025 年 7 月 26 日之后发布的固件都可以)
+
+3、PC端的串口工具,例如SSCOM、LLCOM等都可以;
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件:
+
+    如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+    如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+    如果需要多网卡,打开require "netdrv_multiple",其余注释掉。
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、烧录成功后,自动开机运行,如果出现以下日志,表示WebSocket连接成功:
+``` lua
+I/user.WebSocket发送任务 WebSocket连接成功
+```
+
+5、打开PC端的串口工具,选择对应的端口,配置波特率115200,数据位8,停止位1,无奇偶校验位;勾选“DRT"和"Hex显示"。
+
+6、PC端的串口工具输入"echo",点击发送,WebSocket服务器会回复当前时间信息;
+``` lua
+ 收到WebSocket服务器数据: Wed 2025-08-27 17:34:17
+```
+
+7、PC端的串口工具输入任意数据,点击发送,数据会通过WebSocket发送到服务器;
+
+8、PC端的串口工具,发送一段非"echo"数据,会出现以下日志,并且能看到是WebSocket server发送的,类似于以下效果:
+``` lua
+I/user.准备发送数据到服务器,长度 7
+I/user.原始数据: AAAAA
+I/user.UART发送到服务器的数据包类型 string
+I/user.转发普通数据
+I/user.WebSocket发送任务等待消息 SEND_REQ nil nil
+I/user.WebSocket发送任务 收到发送请求
+I/user.wbs_sender 发送成功 长度 7
+
+```
+
+ 
+

+ 33 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/timer_app.lua

@@ -0,0 +1,33 @@
+--[[
+@module  timer_app
+@summary 定时器应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为定时器应用功能模块,核心业务逻辑为:
+创建一个5秒的循环定时器,每次产生一段数据,通知WebSocket client进行处理;
+
+本文件的对外接口有一个:
+1、sys.publish("SEND_DATA_REQ", "timer", data, {func=send_data_cbfunc, para="timer"..data}),通过publish通知WebSocket client数据发送功能模块发送data数据;
+   数据发送结果通过执行回调函数send_data_cbfunc通知本功能模块;
+]]
+
+local data = 1
+
+-- 数据发送结果回调函数
+local function send_data_cbfunc(result, para)
+    log.info("send_data_cbfunc", result, para)
+    -- 无论上一次发送成功还是失败,启动一个5秒的定时器,5秒后发送下次数据
+    sys.timerStart(send_data_req_timer_cbfunc, 5000)
+end
+
+-- 定时器回调函数
+function send_data_req_timer_cbfunc()
+    -- 发布消息"SEND_DATA_REQ"
+    sys.publish("SEND_DATA_REQ", "timer", data, {func=send_data_cbfunc, para="timer"..data})
+    data = data+1
+end
+
+-- 启动一个5秒的单次定时器
+sys.timerStart(send_data_req_timer_cbfunc, 5000)

+ 69 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/uart_app.lua

@@ -0,0 +1,69 @@
+--[[
+@module  uart_app
+@summary 串口应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为串口应用功能模块,核心业务逻辑为:
+1、打开uart1,波特率115200,数据位8,停止位1,无奇偶校验位;
+2、uart1和pc端的串口工具相连;
+3、从uart1接收到pc端串口工具发送的数据后,通知WebSocket client进行处理;
+4、收到WebSocket client从WebSocket server接收到的数据后,将数据通过uart1发送到pc端串口工具;
+
+本文件的对外接口有两个:
+1、sys.publish("SEND_DATA_REQ", "uart", read_buf),通过publish通知WebSocket client数据发送功能模块发送read_buf数据,不关心数据发送成功还是失败;
+2、sys.subscribe("RECV_DATA_FROM_SERVER", recv_data_from_server_proc),订阅RECV_DATA_FROM_SERVER消息,处理消息携带的数据;
+]]
+
+-- 使用UART1
+local UART_ID = 1
+-- 串口接收数据缓冲区
+local read_buf = ""
+
+-- 将前缀prefix和数据data拼接
+-- 然后末尾增加回车换行两个字符,通过uart发送出去,方便在PC端换行显示查看
+local function recv_data_from_server_proc(prefix, data)
+    uart.write(UART_ID, prefix..data.."\r\n")
+end
+
+local function concat_timeout_func()
+    -- 如果存在尚未处理的串口缓冲区数据;
+    -- 将数据通过publish通知其他应用功能模块处理;
+    -- 然后清空本文件的串口缓冲区数据
+    if read_buf:len() > 0 then
+        sys.publish("SEND_DATA_REQ", "uart", read_buf)
+        log.info("uart_app", "Sending data length:", read_buf:len())
+        log.info("uart_app", "Sending data (hex):", read_buf:toHex())
+        read_buf = ""
+    end
+end
+
+-- UART1的数据接收中断处理函数
+local function read()
+    local s
+    while true do
+        -- 非阻塞读取UART1接收到的数据,最长读取1024字节
+        s = uart.read(UART_ID, 1024)
+        
+        -- 如果从串口没有读到数据
+        if not s or s:len() == 0 then
+            -- 启动50毫秒的定时器,如果50毫秒内没收到新的数据,则处理当前收到的所有数据
+            sys.timerStart(concat_timeout_func, 50)
+            break
+        end
+
+        log.info("uart_app.read len", s:len())
+        -- 将本次从串口读到的数据拼接到串口缓冲区read_buf中
+        read_buf = read_buf..s
+    end
+end
+
+-- 初始化UART1,波特率115200,数据位8,停止位1
+uart.setup(UART_ID, 115200, 8, 1)
+
+-- 注册UART1的数据接收中断处理函数
+uart.on(UART_ID, "receive", read)
+
+-- 订阅"RECV_DATA_FROM_SERVER"消息的处理函数recv_data_from_server_proc
+sys.subscribe("RECV_DATA_FROM_SERVER", recv_data_from_server_proc)

+ 192 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/websocket_main.lua

@@ -0,0 +1,192 @@
+--[[
+@module  websocket_main
+@summary WebSocket client 主应用功能模块
+@version 1.1
+@date    2025.08.24
+@author  陈媛媛
+@usage
+本文件为WebSocket client 主应用功能模块,核心业务逻辑为:
+1、创建一个WebSocket client,连接server;
+2、处理连接/异常逻辑,出现异常后执行重连动作;
+3、调用websocket_receiver的外部接口websocket_receiver.proc,对接收到的数据进行处理;
+4、调用sysplus.sendMsg接口,发送"CONNECT OK"和"DISCONNECTED"两种类型的"WEBSOCKET_EVENT"消息到websocket_sender的task,控制数据发送逻辑;
+5、收到WebSocket数据后,执行sys.publish("FEED_NETWORK_WATCHDOG") 对网络环境检测看门狗功能模块进行喂狗;
+
+本文件没有对外接口,直接在main.lua中require "websocket_main"就可以加载运行;
+]]
+
+-- 加载WebSocket client数据接收功能模块
+local websocket_receiver = require "websocket_receiver"
+-- 加载WebSocket client数据发送功能模块
+local websocket_sender = require "websocket_sender"
+
+-- WebSocket服务器地址和端口
+-- 这里使用的地址和端口,会不定期重启或维护,仅能用作测试用途,不可商用,说不定哪一天就关闭了
+-- 用户开发项目时,替换为自己的商用服务器地址和端口
+-- 加密TCP链接 wss 表示加密
+local SERVER_URL = "wss://echo.airtun.air32.cn/ws/echo"
+-- 这是另外一个测试服务, 能响应websocket的二进制帧
+--local SERVER_URL = "ws://echo.airtun.air32.cn/ws/echo2"
+
+-- websocket_main的任务名
+local TASK_NAME = websocket_sender.TASK_NAME_PREFIX.."main"
+
+-- WebSocket client的事件回调函数
+local function websocket_client_event_cbfunc(ws_client, event, data, fin, opcode)
+    log.info("WebSocket事件回调", ws_client, event, data, fin, opcode)
+
+    -- WebSocket连接成功
+    if event == "conack" then
+        sysplus.sendMsg(TASK_NAME, "WEBSOCKET_EVENT", "CONNECT", true)
+        -- 连接成功,通知网络环境检测看门狗功能模块进行喂狗
+        sys.publish("FEED_NETWORK_WATCHDOG")
+
+    -- 接收到服务器下发的数据
+    -- data:string类型,表示接收到的数据
+    -- fin:number类型,1表示是最后一个数据包,0表示还有后续数据包
+    -- opcode:number类型,表示数据包类型(1-文本,2-二进制)
+    elseif event == "recv" then
+        -- 对接收到的数据处理
+        websocket_receiver.proc(data, fin, opcode)
+
+    -- 发送成功数据
+    -- data:number类型,表示发送状态(通常为nil或0)
+    elseif event == "sent" then
+        log.info("WebSocket事件回调", "数据发送成功,发送确认")
+        -- 发送消息通知 websocket sender task
+        sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "SEND_OK", data)
+
+    -- 服务器断开WebSocket连接
+    elseif event == "disconnect" then
+        -- 发送消息通知 websocket main task
+        sysplus.sendMsg(TASK_NAME, "WEBSOCKET_EVENT", "DISCONNECTED", false)
+
+    -- 严重异常,本地会主动断开连接
+    -- data:string类型,表示具体的异常,有以下几种:
+    --       "connect":tcp连接失败
+    --       "tx":数据发送失败
+    --       "other":其他异常
+    elseif event == "error" then
+        if data == "connect" then
+            -- 发送消息通知 websocket main task,连接失败
+            sysplus.sendMsg(TASK_NAME, "WEBSOCKET_EVENT", "CONNECT", false)
+        elseif data == "other" or data == "tx" then
+            -- 发送消息通知 websocket main task,出现异常
+            sysplus.sendMsg(TASK_NAME, "WEBSOCKET_EVENT", "ERROR")
+        end
+    end
+end
+
+-- websocket main task 的任务处理函数
+local function websocket_client_main_task_func()
+    local ws_client
+    local result, msg
+
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("WebSocket主任务", "等待网络就绪", socket.dft())
+            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+            -- 或者等待1秒超时退出阻塞等待状态;
+            -- 注意:此处的1000毫秒超时不要修改的更长;
+            -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+            -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+            sys.waitUntil("IP_READY", 1000)
+        end
+
+        -- 检测到了IP_READY消息
+        log.info("WebSocket主任务", "收到网络就绪消息", socket.dft())
+
+        -- 清空此task绑定的消息队列中的未处理的消息
+        sysplus.cleanMsg(TASK_NAME)
+
+        -- 创建WebSocket client对象
+        ws_client = websocket.create(nil, SERVER_URL)
+        -- 如果创建WebSocket client对象失败
+        if not ws_client then
+            log.error("WebSocket主任务", "WebSocket创建失败")
+            goto EXCEPTION_PROC
+        end
+
+        -- 设置自定义请求头
+        --如果有需要,根据自己的实际需求,在此处配置请求头并打开注释。
+        --if ws_client.headers then
+           --ws_client:headers({Auth="Basic ABCDEGG"})
+        --end
+
+        -- 注册WebSocket client对象的事件回调函数
+        ws_client:on(websocket_client_event_cbfunc)
+
+        -- 连接server
+        result = ws_client:connect()
+        -- 如果连接server失败
+        if not result then
+            log.error("WebSocket主任务", "WebSocket连接失败")
+            goto EXCEPTION_PROC
+        end
+
+        -- 连接、断开连接、异常等各种事件的处理调度逻辑
+        while true do
+            -- 等待"WEBSOCKET_EVENT"消息
+            msg = sysplus.waitMsg(TASK_NAME, "WEBSOCKET_EVENT")
+            log.info("WebSocket主任务等待消息", msg[2], msg[3], msg[4])
+
+            -- connect连接结果
+            -- msg[3]表示连接结果,true为连接成功,false为连接失败
+            if msg[2] == "CONNECT" then
+                -- WebSocket连接成功
+                if msg[3] then
+                    log.info("WebSocket主任务", "连接成功")
+                    -- 通知websocket sender数据发送应用模块的task,WebSocket连接成功
+                    sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "CONNECT_OK", ws_client)
+                -- WebSocket连接失败
+                else
+                    log.info("WebSocket主任务", "连接失败")
+                    -- 退出循环,发起重连
+                    break
+                end
+
+            -- 需要主动关闭WebSocket连接
+            -- 用户需要主动关闭WebSocket连接时,可以调用sysplus.sendMsg(TASK_NAME, "WEBSOCKET_EVENT", "CLOSE")
+            elseif msg[2] == "CLOSE" then
+                -- 主动断开WebSocket client连接
+                ws_client:disconnect()
+                -- 发送disconnect之后,此处延时1秒,给数据发送预留一点儿时间
+                sys.wait(1000)
+                break
+
+            -- 被动关闭了WebSocket连接
+            -- 被网络或者服务器断开了连接
+            elseif msg[2] == "DISCONNECTED" then
+                break
+
+            -- 出现了其他异常
+            elseif msg[2] == "ERROR" then
+                break
+            end
+        end
+
+        -- 出现异常
+        ::EXCEPTION_PROC::
+
+        -- 清空此task绑定的消息队列中的未处理的消息
+        sysplus.cleanMsg(TASK_NAME)
+
+        -- 通知websocket sender数据发送应用模块的task,WebSocket连接已经断开
+        sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "DISCONNECTED")
+
+        -- 如果存在WebSocket client对象
+        if ws_client then
+            -- 关闭WebSocket client,并且释放WebSocket client对象
+            ws_client:close()
+            ws_client = nil
+        end
+
+        -- 5秒后跳转到循环体开始位置,自动发起重连(与MQTT保持一致)
+        sys.wait(5000)
+    end
+end
+
+--创建并且启动一个task
+sysplus.taskInitEx(websocket_client_main_task_func, TASK_NAME)

+ 77 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/websocket_receiver.lua

@@ -0,0 +1,77 @@
+--[[
+@module  websocket_receiver
+@summary WebSocket client数据接收处理应用功能模块
+@version 1.0
+@date    2025.08.24
+@author  陈媛媛
+@usage
+本文件为WebSocket client 数据接收应用功能模块,核心业务逻辑为:
+处理接收到的数据,同时将数据发送给其他应用功能模块做进一步处理;
+
+本文件的对外接口有2个:
+1、websocket_receiver.proc(data, fin, opcode):数据处理入口,在websocket_main.lua中调用;
+2、sys.publish("RECV_DATA_FROM_SERVER", "recv from websocket server: ", data):
+   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
+   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
+]]
+
+local websocket_receiver = {}
+
+-- 接收数据缓冲区
+local recv_data_buff = ""
+
+--[[
+处理接收到的数据
+
+@api websocket_receiver.proc(data, fin, opcode)
+
+@param1 data string
+表示接收到的数据
+
+@param2 fin number
+表示是否为最后一个数据包,1表示是最后一个,0表示还有后续
+
+@param3 opcode number
+表示数据包类型,1-文本,2-二进制
+
+@return1 result nil
+
+@usage
+websocket_receiver.proc(data, fin, opcode)
+]]
+function websocket_receiver.proc(data, fin, opcode)
+    log.info("WebSocket接收处理", "收到数据", data, "是否结束", fin, "操作码", opcode)
+
+    -- 接收到数据,通知网络环境检测看门狗功能模块进行喂狗
+    sys.publish("FEED_NETWORK_WATCHDOG")
+
+    -- 将数据拼接到缓冲区
+    recv_data_buff = recv_data_buff .. data
+
+    -- 如果收到完整消息(fin=1)并且缓冲区有数据,则处理
+    if fin == 1 and #recv_data_buff > 0 then
+        local processed_data = recv_data_buff
+        recv_data_buff = "" -- 清空缓冲区
+
+        -- 尝试解析JSON格式数据
+        local json_data, result, errinfo = json.decode(processed_data)
+        if result and type(json_data) == "table" then
+            log.info("WebSocket接收处理", "收到JSON格式数据")
+            -- 如果是JSON格式,提取有用信息
+            if json_data.action == "echo" and json_data.msg then
+                processed_data = json_data.msg
+                log.info("WebSocket接收处理", "提取echo消息", processed_data)
+            end
+            -- 其他JSON格式数据处理逻辑可以在这里添加
+        else
+            log.info("WebSocket接收处理", "收到非JSON格式数据")
+        end
+
+        -- 将处理后的数据通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
+        sys.publish("RECV_DATA_FROM_SERVER", "收到WebSocket服务器数据: ", processed_data)
+    else
+        log.info("WebSocket接收处理", "收到部分数据,等待后续数据包")
+    end
+end
+
+return websocket_receiver

+ 208 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/WebSocket/websocket_sender.lua

@@ -0,0 +1,208 @@
+--[[
+@module  websocket_sender
+@summary WebSocket client数据发送应用功能模块
+@version 1.0
+@date    2025.08.25
+@author  陈媛媛
+@usage
+本文件为WebSocket client 数据发送应用功能模块,核心业务逻辑为:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、websocket sender task接收"CONNECT_OK"、"SEND_REQ"、"SEND_OK"三种类型的"WEBSOCKET_EVENT"消息,遍历队列send_queue,逐条发送数据到server;
+3、websocket sender task接收"DISCONNECTED"类型的"WEBSOCKET_EVENT"消息,丢弃掉队列send_queue中未发送的数据;
+4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
+
+本文件的对外接口有1个:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
+   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
+   本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+]]
+
+local websocket_sender = {}
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+    [3] = {data="data3", cb={func=callback_function3, para=callback_para3}},
+}
+data的内容为要发送的数据,string类型,必须存在;
+cb.func的内容为数据发送结果的用户回调函数,可以不存在;
+cb.para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
+]]
+
+local send_queue = {}
+
+-- WebSocket client的任务名前缀
+websocket_sender.TASK_NAME_PREFIX = "websocket_"
+
+-- websocket_client_sender的任务名
+websocket_sender.TASK_NAME = websocket_sender.TASK_NAME_PREFIX.."sender"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 确保data是字符串类型
+    local data_str = tostring(data)
+    
+    -- 检查是否是"echo"命令
+    if data_str == '"echo"' then
+        log.info("WebSocket发送处理", "收到echo命令,发送数据")
+        -- 创建JSON格式的echo响应
+        local response = json.encode({
+            action = "echo",
+            msg = os.date("%a %Y-%m-%d %H:%M:%S") -- %a表示星期几缩写
+        })
+        -- 将echo响应插入到发送队列send_queue中
+        table.insert(send_queue, {data=response, cb=cb})
+        log.info("准备发送数据到服务器,长度", #response)
+        log.info("原始数据:", response)
+    else
+        -- 根据tag类型输出日志
+        if tag == "timer" then
+            -- 对于timer数据,修改日志为"发送心跳"
+            log.info("发送心跳", "长度", #data_str)
+            log.info("原始数据:", data_str)
+            table.insert(send_queue, {data=data_str, cb=cb})
+        else
+            -- 其他数据(如uart)
+            log.info("准备发送数据到服务器,长度", #data_str)
+            log.info("原始数据:", data_str)
+            log.info("UART发送到服务器的数据包类型", type(data_str))
+            log.info("转发普通数据")
+            table.insert(send_queue, {data=data_str, cb=cb})
+        end
+    end
+    
+    -- 发送消息通知 websocket sender task,有新数据等待发送
+    sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "SEND_REQ")
+end
+
+-- 按照顺序发送send_queue中的数据
+-- 如果调用send接口成功,则返回当前正在发送的数据项
+-- 如果调用send接口失败,通知回调函数发送失败后,继续发送下一条数据
+local function send_item(ws_client)
+    local item
+    -- 如果发送队列中有数据等待发送
+    while #send_queue > 0 do
+        -- 取出来第一条数据赋值给item
+        -- 同时从队列send_queue中删除这一条数据
+        item = table.remove(send_queue, 1)
+        
+        -- 检查WebSocket连接状态
+        if not ws_client or not ws_client:ready() then
+            log.warn("WebSocket发送处理", "WebSocket连接未就绪,无法发送")
+            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+            if item.cb and item.cb.func then
+                item.cb.func(false, item.cb.para)
+            end
+            -- 触发重连
+            sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "DISCONNECTED")
+            return nil
+        end
+
+        -- send数据
+        -- result表示调用send接口的同步结果,返回值有以下几种:
+        -- 如果失败,返回false
+        -- 如果成功,返回true
+        result = ws_client:send(item.data)
+
+        -- send接口调用成功
+        if result then
+            -- 根据数据内容修改日志输出
+            if item.data:match("^%d+$") then -- 如果数据是纯数字(来自timer)
+                log.info("wbs_sender", "发送心跳成功", "长度", #item.data)
+            else
+                log.info("wbs_sender", "发送成功", "长度", #item.data)
+            end
+            
+            -- 由于sent事件可能不会触发,我们直接认为发送成功
+            if item.cb and item.cb.func then
+                item.cb.func(true, item.cb.para)
+            end
+            -- 发送成功,通知网络环境检测看门狗功能模块进行喂狗
+            -- 使用来自定时器的数据作为心跳
+            if item.data:match("^%d+$") then -- 如果数据是纯数字(来自timer)
+                sys.publish("FEED_NETWORK_WATCHDOG")
+            end
+            return item
+        -- send接口调用失败
+        else
+            log.warn("WebSocket发送处理", "数据发送失败")
+            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+            if item.cb and item.cb.func then
+                item.cb.func(false, item.cb.para)
+            end
+            -- 触发重连
+            sysplus.sendMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT", "DISCONNECTED")
+            return nil
+        end
+    end
+    return nil
+end
+
+-- websocket client sender的任务处理函数
+local function websocket_client_sender_task_func()
+    local ws_client
+    local send_item_obj
+    local result, msg
+
+    while true do
+        -- 等待"WEBSOCKET_EVENT"消息
+        msg = sysplus.waitMsg(websocket_sender.TASK_NAME, "WEBSOCKET_EVENT")
+        log.info("WebSocket发送任务等待消息", msg[2], msg[3])
+
+        -- WebSocket连接成功
+        -- msg[3]表示WebSocket client对象
+        if msg[2] == "CONNECT_OK" then
+            ws_client = msg[3]
+            log.info("WebSocket发送任务", "WebSocket连接成功")
+            -- 发送send_queue中的所有数据
+            while #send_queue > 0 do
+                send_item_obj = send_item(ws_client)
+                if not send_item_obj then
+                    break
+                end
+            end
+
+        -- WebSocket send数据请求
+        elseif msg[2] == "SEND_REQ" then
+            log.info("WebSocket发送任务", "收到发送请求")
+            -- 如果WebSocket client对象存在
+            if ws_client then
+                send_item_obj = send_item(ws_client)
+            end
+
+        -- WebSocket send数据成功
+        elseif msg[2] == "SEND_OK" then
+            log.info("WebSocket发送任务", "数据发送成功")
+            -- 继续发送send_queue中的数据
+            send_item_obj = send_item(ws_client)
+
+        -- WebSocket断开连接
+        elseif msg[2] == "DISCONNECTED" then
+            log.info("WebSocket发送任务", "WebSocket连接断开")
+            -- 清空WebSocket client对象
+            ws_client = nil
+            -- 如果发送队列中有数据等待发送
+            while #send_queue > 0 do
+                -- 取出来第一条数据赋值给send_item_obj
+                -- 同时从队列send_queue中删除这一条数据
+                send_item_obj = table.remove(send_queue, 1)
+                -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+                if send_item_obj.cb and send_item_obj.cb.func then
+                    send_item_obj.cb.func(false, send_item_obj.cb.para)
+                end
+            end
+            -- 当前没有正在等待发送结果的发送项
+            send_item_obj = nil
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+--创建并且启动一个task
+sysplus.taskInitEx(websocket_client_sender_task_func, websocket_sender.TASK_NAME)
+
+return websocket_sender