陈取德 пре 1 месец
родитељ
комит
fe62c5c4d4
35 измењених фајлова са 2821 додато и 757 уклоњено
  1. 127 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/low_power.lua
  2. 0 87
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/lowpower_dissipation.lua
  3. 75 15
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/main.lua
  4. 0 67
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/normal.lua
  5. 69 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/normal_power.lua
  6. 143 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/psm+_power.lua
  7. 66 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/readme.md
  8. 140 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_main.lua
  9. 72 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_receiver.lua
  10. 110 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_sender.lua
  11. 145 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_short.lua
  12. 0 76
      module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/ultra_low_power.lua
  13. 127 0
      module/Air780EPM/demo/lowpower/low_power.lua
  14. 0 88
      module/Air780EPM/demo/lowpower/lowpower_dissipation.lua
  15. 75 15
      module/Air780EPM/demo/lowpower/main.lua
  16. 0 67
      module/Air780EPM/demo/lowpower/normal.lua
  17. 69 0
      module/Air780EPM/demo/lowpower/normal_power.lua
  18. 144 0
      module/Air780EPM/demo/lowpower/psm+_power.lua
  19. 66 0
      module/Air780EPM/demo/lowpower/readme.md
  20. 140 0
      module/Air780EPM/demo/lowpower/tcp_client_main.lua
  21. 72 0
      module/Air780EPM/demo/lowpower/tcp_client_receiver.lua
  22. 110 0
      module/Air780EPM/demo/lowpower/tcp_client_sender.lua
  23. 145 0
      module/Air780EPM/demo/lowpower/tcp_short.lua
  24. 0 76
      module/Air780EPM/demo/lowpower/ultra_low_power.lua
  25. 112 104
      module/Air8000/demo/lowpower/low_power.lua
  26. 75 15
      module/Air8000/demo/lowpower/main.lua
  27. 0 68
      module/Air8000/demo/lowpower/normal.lua
  28. 69 0
      module/Air8000/demo/lowpower/normal_power.lua
  29. 144 0
      module/Air8000/demo/lowpower/psm+_power.lua
  30. 59 0
      module/Air8000/demo/lowpower/readme.md
  31. 140 0
      module/Air8000/demo/lowpower/tcp_client_main.lua
  32. 72 0
      module/Air8000/demo/lowpower/tcp_client_receiver.lua
  33. 110 0
      module/Air8000/demo/lowpower/tcp_client_sender.lua
  34. 145 0
      module/Air8000/demo/lowpower/tcp_short.lua
  35. 0 79
      module/Air8000/demo/lowpower/ultra_low_power.lua

+ 127 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/low_power.lua

@@ -0,0 +1,127 @@
+--[[
+@module  low_power
+@summary 低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否在低功耗模式下使用手机卡、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "low_power"就可以加载运行;
+]] --
+----是否需要插入手机卡,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = false -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI",lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
+
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    
+    -- 关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
+    pm.power(pm.USB, false)
+end
+
+--[[
+本函数为low_power低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否开启4G、连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function low_power_func()
+    log.info("开始测试低功耗模式功耗。")
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 进入低功耗 MODE 1 模式
+        pm.power(pm.WORK_MODE, 1)
+        -- 判断是否连接TCP平台
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
+        end
+    else
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 如果不开启4G,则进入飞行模式再进入低功耗模式,保持环境干净。
+        mobile.flymode(0, true)
+        pm.power(pm.WORK_MODE, 1)
+    end
+end
+
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
+    end
+end
+
+sys.taskInit(low_power_func)

+ 0 - 87
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/lowpower_dissipation.lua

@@ -1,87 +0,0 @@
-
--- netlab.luatos.com上打开TCP 有测试服务器
-local server_ip = "112.125.89.8"
-local server_port = 47523
-local is_udp = false --用户根据自己实际情况选择
-
---是UDP服务器就赋值为true,是TCP服务器就赋值为flase
---UDP服务器比TCP服务器功耗低
---如果用户对数据的丢包率有极为苛刻的要求,最好选择TCP
-
-local Heartbeat_interval = 5 -- 发送数据的间隔时间,单位分钟
-
--- 数据内容  
-local heart_data = string.rep("1234567890", 10)
-local rxbuf = zbuff.create(8192)
-
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-    if event == socket.LINK then
-    elseif event == socket.ON_LINE then
-        -- 链接上服务器以后发送的第一包数据是 hello,luatos
-        socket.tx(netc, "hello,luatos!")
-
-    elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0, rxbuf:used()), "数据长度", rxbuf:used())
-        end
-        rxbuf:del()
-    elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-    elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
-    end
-end
-
-local function socketTask()
-    local netc = socket.create(nil, netCB) --创建一个链接
-    socket.debug(netc, true)--开启socket层的debug日志,方便寻找问题
-    socket.config(netc, nil, is_udp, nil, 300, 5, 6)  --配置TCP链接的参数,开启保活,防止长时间无数据交互服务器踢掉模块
-    while true do
-        --真正去链接服务器
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        --链接成功后循环发送数据
-        while succ do
-            local Heartbeat_interval = Heartbeat_interval * 60 * 1000
-            sys.wait(Heartbeat_interval)
-            socket.tx(netc, heart_data)
-        end
-        --链接不成功5S重连一次
-        if not succ then
-            log.info("未知错误,5秒后重连")
-            uart.write(1, "未知错误,5秒后重连")
-        else
-            local result, msg = sys.waitUntil("socket_disconnect")
-        end
-        log.info("服务器断开了,5秒后重连")
-        uart.write(1, "服务器断开了,5秒后重连")
-        socket.close(netc)
-        sys.wait(5000)
-    end
-end
-
-function socketDemo()
-
-    --配置GPIO以达到最低功耗的目的
-    gpio.setup(23, nil)
-    gpio.close(33) -- 如果功耗偏高,开始尝试关闭WAKEUPPAD1
-    gpio.close(35) -- 这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
-    pm.power(pm.USB, false)
-
-     --进入低功耗长连接模式
-    pm.power(pm.WORK_MODE, 1)
-
-    sys.taskInit(socketTask)
-
-end
-
-sys.taskInit(socketDemo)
-

+ 75 - 15
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/main.lua

@@ -1,17 +1,77 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "socket_low_power"
-VERSION = "1.0"
-PRODUCT_KEY = "123" --换成自己的
--- sys库是标配
-_G.sys = require("sys")
-_G.sysplus = require("sysplus")
-log.style(1)
-
---require "normal" --正常模式
---require "lowpower_dissipation" --低功耗模式
-require "ultra_low_power" --超低功耗模式(PSM+模式)
-
--- 用户代码已结束---------------------------------------------
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本demo演示的核心功能为:
+三种低功耗模式代码演示和功耗体验
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,持续向平台发送心跳数据。平均功耗:6.6mA
+2、low_power低功耗模式:low_powerr.lua中就是低功耗模式的代码案例,进入低功耗模式后向平台发送心跳包。DTIM1模式平均功耗:1.5mA。DTIM10模式平均功耗380uA。
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,定时唤醒向平台发送心跳包。平均功耗:11uA
+更多说明参考本目录下的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 = "LOWPOWER"
+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)
+
+
+--选择需要体验的功耗模式,注释另外两个代码即可!快捷键Ctrl + /
+-- require "normal_power"
+-- require "low_power"
+require "psm+_power"
+
+
+-- 用户代码已结束---------------------------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 67
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/normal.lua

@@ -1,67 +0,0 @@
-
--- netlab.luatos.com上打开TCP,然后修改IP和端口号,自动回复netlab下发的数据,自收自发测试
-
-local server_ip = "112.125.89.8"
-local server_port = 47523
-
-local rxbuf = zbuff.create(8192)
-
-sys.subscribe("IP_READY", function(ip, adapter)
-    log.info("mobile", "IP_READY", ip, (adapter or -1) == socket.LWIP_GP)
-    sys.publish("net_ready")
-end)
-
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-	if event == socket.LINK then
-	elseif event == socket.ON_LINE then
-        socket.tx(netc, "hello,luatos!")
-	elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0,rxbuf:used()):toHex())
-            log.info("发送", rxbuf:used(), "bytes")
-            socket.tx(netc, rxbuf)
-        end
-        rxbuf:del()
-	elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-	elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
-    end
-end
-
-local function socketTask()
-    sys.waitUntil("net_ready")
-    log.info("联网成功,准备链接服务器")
-    pm.power(pm.WORK_MODE,0) --进入正常模式
-	local netc = socket.create(nil, netCB)
-	socket.debug(netc, true)
-	socket.config(netc, nil, nil, nil, 300, 5, 6)   --开启TCP保活,防止长时间无数据交互被运营商断线
-    while true do
-        log.info("开始链接服务器")
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        if not succ then
-            log.info("未知错误,5秒后重连")
-        else
-            log.info("链接服务器成功")
-            local result, msg = sys.waitUntil("socket_disconnect")
-        end
-        log.info("服务器断开了,5秒后重连")
-        socket.close(netc)
-        log.info(rtos.meminfo("sys"))
-        sys.wait(5000)
-    end
-end
-
-function socketDemo()
-	sys.taskInit(socketTask)
-end
-
-
-socketDemo()

+ 69 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/normal_power.lua

@@ -0,0 +1,69 @@
+--[[
+@module  normal_power
+@summary 常规模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为常规模式主应用功能模块,核心业务逻辑为:
+1、进入常规模式
+2、判断是否开启4G、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "normal_power"就可以加载运行;
+]] --
+----是否需要开启4G,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = true -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+function normal_power_func()
+    log.info("开始测试常规模式功耗。")
+    -- 将电源模式调整为常规模式。
+    pm.power(pm.WORK_MODE, 0)
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 判断是否连接TCP平台。
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
+        end
+    else
+        -- 开启飞行模式,保持环境干净。
+        mobile.flymode(0, true)
+    end
+end
+
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft())do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:",mobile.status(),"。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
+    end
+end
+
+sys.taskInit(normal_power_func)

+ 143 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/psm+_power.lua

@@ -0,0 +1,143 @@
+--[[
+@module  psm+_power
+@summary psm+超低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为psm+超低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "psm+_power"就可以加载运行;
+]] --
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = false -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+-- GPIO唤醒函数,用于配置WAKEUP管脚使用
+local function gpio_wakeup()
+    log.info("gpio_wakeup")
+end
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+3、配置三种模块唤醒方式:
+    1)WAKEUP管脚:配置为中断拉低触发唤醒,可用于外部触发唤醒模块;
+    2)dtimerStart:配置休眠定时器,根据预设时间唤醒模块;
+    3)UART1:配置为9600波特率,可用于通过串口发送指令唤醒模块;
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI",lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP0, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP1, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP2, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP3, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP4, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.PWR_KEY, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 配置UART1为9600波特率,可用于唤醒PSM+模式下的模块;
+    -- uart.setup(1,9600)
+
+    -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块;
+    -- pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+
+    -- 从2025年3月份开始的固件版本在pm.power(pm.WORK_MODE, 3)中会自动控制USB和飞行模式,脚本里不需要再手动控制
+    -- pm.power(pm.USB, false)
+    -- mobile.flymode(0, true)
+end
+
+--[[
+本函数为psm+超低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function psm_power_func()
+    log.info("开始测试PSM+模式功耗。")
+    -- 判断是否连接TCP平台。
+    if tcp_mode then
+        -- 导入短连接tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+        require "tcp_short"
+        -- 向指定taskName任务发送一个消息,可以解除指定taskName种的sys.waitMsg的阻塞状态。
+        sys.sendMsg("tcp_short", "data", heart_data)
+        -- 等待短连接TCP功能模块任务完成,获取"tcp_short_result"信息中的发送状态和接收信息
+        local result, send_result, rec_data = sys.waitUntil("tcp_short_result")
+        log.info("信息发送结果:", send_result, "接收到的信息:", rec_data)
+        -- 判断完有没有发送成功后都进入PSM+模式,减少功耗损耗。
+        -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块上传心跳信息。2
+        pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+    end
+    GPIO_setup()
+    -- V2018及以前的版本固件因PSM+模式处理飞行模式逻辑修改,需要增加等待,确保飞行模式执行完成,不然会死机被底层看门狗重启
+    -- V2019及以后的版本固件不需要增加等待
+    sys.wait(500)
+    -- 执行到这条代码后,CPU关机,后续代码不会执行。
+    pm.power(pm.WORK_MODE, 3)
+    -- 下面几行代码实现的功能是:延时等待80秒,然后软件重启;
+    --
+    -- 为什么要写下面三行代码,分以下两种情况介绍:
+    -- 1、当上面执行pm.power(pm.WORK_MODE, 3)时,如果立即成功进入到PSM+模式,则没有机会执行此处的三行代码
+    -- 2、当上面执行pm.power(pm.WORK_MODE, 3)时,如果没有立即成功进入到PSM+模式,则会接着执行此处的三行代码
+    --    在此处最多等待80秒,80秒内,如果成功进入了PSM+模式,则没有机会运行此处的rtos.reboot()软件重启代码
+    --    如果超过了80秒,都没有成功进入PSM+模式,则此处会控制软件重启,
+    --    重启后,根据本demo项目写的业务逻辑,会再次执行进入PSM+模式的逻辑
+    --
+    -- 此处为什么延时等待80秒,是因为从2025年3月份开始,对外发布的内核固件,在脚本中执行pm.power(pm.WORK_MODE, 3)时,
+    -- 如果满足进入PSM+模式的条件,理论上会立即成功进入PSM+模式,
+    -- 虽然如此,为了防止出现不可预知的错误,所以在内核固件中多加了一层保护机制,最长等待75秒钟一定会成功进入PSM+模式
+    -- 所以在此处延时等待80秒,比75秒稍微长个5秒钟,为了让内核固件的这一层保护机制有时间运行,而不必执行此处的软件重启脚本代码
+    -- 减少不必要的重启规避逻辑而带来的多余功耗
+    --
+    -- 这个是设置允许进入PSM+模式的标准操作,必须要与demo保持一致,加入这三行代码
+    sys.wait(80000)
+    log.info("psm_app_task", "进入PSM+失败,重启")
+    rtos.reboot()
+end
+
+sys.taskInit(psm_power_func)

+ 66 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/readme.md

@@ -0,0 +1,66 @@
+## 演示功能概述
+
+本DEMO演示的核心功能为:
+
+Air780EPM/EHM/EHV/EGH 核心板在常规模式、低功耗模式、PSM+模式的功耗表现;
+
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,5分钟一次向平台发送心跳数据。平均功耗:6mA,无业务待机功耗平均:5mA
+
+2、low_power低功耗模式:low_power.lua中就是低功耗模式的代码案例,进入低功耗模式后5分钟一次向平台发送心跳包。low_power低功耗模式平均功耗:1.5mA,无业务待机功耗:300uA
+
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,5分钟一次唤醒向平台发送心跳包。PSM+极低功耗模式平均功耗:3uA,无业务待机功耗:3uA
+
+## 核心板资料
+
+[Air780EPM/EHM核心板](https://docs.openluat.com/air780epm/product/shouce/)
+
+[Air780EHV核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[Air780EGH核心板](https://docs.openluat.com/air780egh/product/shouce/)
+
+## 演示硬件环境
+
+1、Air780EPM/EHM/EHV/EGH 核心板
+
+2、Air9000P功耗分析仪
+
+3、接线方式
+
+ - Air9000P连接核心板的VBAT和GND接口,低功耗模式需要使用外部供电才能测试;
+
+ - 将USB旁边的拨码开关拨到OFF,断开USB供电;
+
+ - 电脑打开 “ 功耗分析仪 ” 软件,连接Air9000P;
+
+## 演示软件环境
+
+1、Luatools下载调试工具;
+
+2、
+[Air780EPM/EHM核心板](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+[Air780EHV核心板](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[Air780EGH核心板](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、demo脚本代码:
+
+ - tcp_client_main.lua中的上方,修改好 “ SERVER_ADDR ”  “ SERVER_PORT ” 参数,修改为自己测试的TCP服务器IP地址和端口号;
+
+ - low_power.lua \ normal.lua 中的上方,可根据业务需求可以通过参数设置是否开启4G功能、连接TCP服务器功能、心跳数据包,演示对应需求的功耗情况;
+
+ - psm+_power.lua 中调用的是 tcp_short.lua 短连接TCP客户端功能模块,在每次发送信息结束后释放TCP客户端,避免造成TCP客户端资源占用情况,修改好“ SERVER_ADDR ”  “ SERVER_PORT ” 参数  “tcp_rec” 参数 ,即可正常测试。
+
+3、Luatools烧录内核固件和修改后的demo脚本代码;
+
+4、烧录成功后,自动开机运行;
+
+5、USB会漏电,所以烧录成功后先观察Luatools的打印,如果输出 “ D/pm workmode X ( X 是代码中pm.power(pm.WORK_MODE, X)所填的数值 )” 表示已经进入对应的功耗模式,即可拔掉USB,重启核心板,通过观察功耗分析仪的平均功耗值观察是否进入低功耗模式。
+
+## 文档网址
+
+https://e3zt58hesn.feishu.cn/wiki/Cwk2wEcN9if4kUk4iQ7czMhwnug?from=from_copylink

+ 140 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_main.lua

@@ -0,0 +1,140 @@
+--[[
+@module  tcp_client_main
+@summary tcp client socket主应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket主应用功能模块,核心业务逻辑为:
+1、创建一个tcp client socket,连接server;
+2、处理连接异常,出现异常后执行重连动作;
+3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
+
+本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
+]] local libnet = require "libnet"
+
+-- 加载tcp client socket数据接收功能模块
+local tcp_client_receiver = require "tcp_client_receiver"
+-- 加载tcp client socket数据发送功能模块
+local tcp_client_sender = require "tcp_client_sender"
+
+-- 电脑访问:https://netlab.luatos.com/
+-- 点击 打开TCP 按钮,会创建一个TCP server
+-- 将server的地址和端口赋值给下面这两个变量
+local SERVER_ADDR = "112.125.89.8"
+local SERVER_PORT = 46837
+
+-- tcp_client_main的任务名
+local TASK_NAME = tcp_client_sender.TASK_NAME
+
+-- 处理未识别的消息
+local function tcp_client_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+
+-- tcp client socket的任务处理函数
+local function tcp_client_main_task_func()
+
+    local socket_client
+    local result, para1, para2
+
+    while true do
+        while not socket.adapter(socket.dft()) do
+            -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+            -- 或者等待30秒超时退出阻塞等待状态
+            log.warn("tcp_client_main_task_func", "wait IP_READY")
+            local mobile_result = sys.waitUntil("IP_READY", 30000)
+            if mobile_result then
+                log.info("4G已经连接成功。")
+            else
+                log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+                -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+                mobile.flymode(0, true)
+                mobile.flymode(0, false)
+            end
+        end
+        -- 检测到了IP_READY消息
+        log.info("tcp_client_main_task_func", "recv IP_READY")
+
+        -- 创建socket client对象
+        socket_client = socket.create(nil, TASK_NAME)
+        -- 如果创建socket client对象失败
+        if not socket_client then
+            log.error("tcp_client_main_task_func", "socket.create error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 配置socket client对象为tcp client
+        result = socket.config(socket_client)
+        -- 如果配置失败
+        if not result then
+            log.error("tcp_client_main_task_func", "socket.config error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 连接server
+        result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
+        -- 如果连接server失败
+        if not result then
+            log.error("tcp_client_main_task_func", "libnet.connect error")
+            goto EXCEPTION_PROC
+        end
+
+        log.info("tcp_client_main_task_func", "libnet.connect success")
+
+        -- 数据收发以及网络连接异常事件总处理逻辑
+        while true do
+            -- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
+            -- 如果处理失败,则退出循环
+            if not tcp_client_receiver.proc(socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
+                break
+            end
+
+            -- 数据发送处理
+            -- 如果处理失败,则退出循环
+            if not tcp_client_sender.proc(TASK_NAME, socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
+                break
+            end
+
+            -- 阻塞等待socket.EVENT事件或者15秒钟超时
+            -- 以下三种业务逻辑会发布事件:
+            -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+            -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+            -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+            result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
+            log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
+
+            -- 如果连接异常,则退出循环
+            if not result then
+                log.warn("tcp_client_main_task_func", "connection exception")
+                break
+            end
+        end
+
+        -- 出现异常    
+        ::EXCEPTION_PROC::
+
+        -- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
+        tcp_client_sender.exception_proc()
+
+        -- 如果存在socket client对象
+        if socket_client then
+            -- 关闭socket client连接
+            libnet.close(TASK_NAME, 5000, socket_client)
+
+            -- 释放socket client对象
+            socket.release(socket_client)
+            socket_client = nil
+        end
+
+        -- 5秒后跳转到循环体开始位置,自动发起重连
+        sys.wait(5000)
+    end
+end
+
+-- 创建并且启动一个task
+-- 运行这个task的主函数tcp_client_main_task_func
+sysplus.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)
+

+ 72 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_receiver.lua

@@ -0,0 +1,72 @@
+--[[
+@module  tcp_client_receiver
+@summary tcp client socket数据接收应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
+从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;
+
+本文件的对外接口有2个:
+1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
+2、sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data):
+   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
+   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
+]]
+
+local tcp_client_receiver = {}
+
+-- socket数据接收缓冲区
+local recv_buff = nil
+
+-- 数据接收应用入口函数
+function tcp_client_receiver.proc(socket_client)
+    -- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
+    if recv_buff==nil then
+        recv_buff = zbuff.create(1024)
+        -- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
+        -- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
+        -- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
+    end
+
+    -- 循环从内核的缓冲区读取接收到的数据
+    -- 如果读取失败,返回false,退出
+    -- 如果读取成功,处理数据,并且继续循环读取
+    -- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
+    while true do
+        -- 从内核的缓冲区中读取数据到recv_buff中
+        -- 如果recv_buff的存储空间不足,会自动扩容
+        local result = socket.rx(socket_client, recv_buff)
+
+        -- 读取数据失败
+        -- 有两种情况:
+        -- 1、recv_buff扩容失败
+        -- 2、socket client和server之间的连接断开
+        if not result then
+            log.error("tcp_client_receiver.proc", "socket.rx error")
+            return false
+        end
+
+        -- 如果读取到了数据, used()就必然大于0, 进行处理
+        if recv_buff:used() > 0 then
+            log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
+
+            -- 读取socket数据接收缓冲区中的数据,赋值给data
+            local data = recv_buff:query()
+
+            -- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
+            sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
+
+            -- 清空socket数据接收缓冲区中的数据
+            recv_buff:del()
+            -- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
+        else
+            break
+        end
+    end
+
+    return true
+end
+
+return tcp_client_receiver

+ 110 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_client_sender.lua

@@ -0,0 +1,110 @@
+--[[
+@module  tcp_client_sender
+@summary tcp client socket数据发送应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server;
+3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据;
+4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
+
+本文件的对外接口有3个:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
+   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+   本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
+3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
+]]
+
+local tcp_client_sender = {}
+
+local libnet = require "libnet"
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+}
+data的内容为真正要发送的数据,必须存在;
+func的内容为数据发送结果的用户回调函数,可以不存在
+para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
+]]
+local send_queue = {}
+
+-- tcp_client_main的任务名
+tcp_client_sender.TASK_NAME = "tcp_client_main"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 将原始数据增加前缀,然后插入到发送队列send_queue中
+    table.insert(send_queue, {data="send from "..tag..": "..data, cb=cb})
+    -- 通知tcp_client_main主任务有数据需要发送
+    -- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
+    sysplus.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
+end
+
+-- 数据发送应用逻辑处理入口
+function tcp_client_sender.proc(task_name, socket_client)
+    local send_item
+    local result, buff_full
+
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        -- 取出来第一条数据赋值给send_item
+        -- 同时从队列send_queue中删除这一条数据
+        send_item = table.remove(send_queue,1)
+
+        -- 发送这条数据,超时时间15秒钟
+        result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
+
+        -- 发送失败
+        if not result then
+            log.error("tcp_client_sender.proc", "libnet.tx error")
+
+            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+            if send_item.cb and send_item.cb.func then
+                send_item.cb.func(false, send_item.cb.para)
+            end
+
+            return false
+        end
+
+        -- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
+        if buff_full then
+            log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
+            table.insert(send_queue, 1, send_item)
+            return true
+        end
+
+        log.info("tcp_client_sender.proc", "send success")
+        -- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(true, send_item.cb.para)
+        end
+    end
+
+    return true
+end
+
+-- 数据发送应用逻辑异常处理入口
+function tcp_client_sender.exception_proc()
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        local send_item = table.remove(send_queue,1)
+        -- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(false, send_item.cb.para)
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+return tcp_client_sender

+ 145 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/tcp_short.lua

@@ -0,0 +1,145 @@
+--[[
+@module  tcp_short
+@summary 短连接TCP客户端功能模块
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件实现了一个短连接TCP客户端,核心功能:
+1. 等待特定消息触发TCP连接
+2. 建立连接后发送队列中的数据
+3. 完成发送后阻塞3秒,等待平台下发的返回信息
+4. 3秒后如果没收到则默认平台无返回信息,随机释放客户端,避免造成过多功耗浪费
+5. 适用于低功耗场景下的周期性数据上报
+本模块功能具备以下对外接口
+1. 可以通过推送消息给"tcp_short",触发消息自动发送。
+2. 通过订阅"tcp_short_close"主题,可以知道tcp_short功能模块任务完成并释放了tcp客户端,并且消息还携带了信息发送状态和平台返回信息
+]] -- 
+-- 服务器地址配置
+local SERVER_ADDR = "112.125.89.8"
+-- 服务器端口配置
+local SERVER_PORT = 44706
+-- 任务名称,用于标识当前TCP客户端任务
+local tcp_short = "tcp_short"
+
+-- 引入网络库,提供TCP通信功能
+local libnet = require "libnet"
+
+local tcp_rec = true
+local recv_buff = nil -- 接收消息缓存区
+local socket_client -- 声明socket客户端对象
+local result, rec_result -- 声明TCP连接任务结果,接收消息结果
+local send_data, rec_data , _  -- 声明发送数据的变量和收到的数据变量
+
+--[[
+@function tcp_short_main_cbfunc
+@summary TCP客户端主任务回调函数
+@param msg 消息内容数组
+@usage 用于处理来自系统的消息通知
+]]
+local function tcp_short_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+--[[
+函数名称: tcp_rec_short
+功能描述: 短连接TCP数据接收处理函数
+          接收TCP下发的数据,接收成功后取出数据给rec_data
+--]]
+local function tcp_rec_short()
+    -- 检查接收缓冲区是否存在,如果不存在则创建1024字节的缓冲区
+    if recv_buff == nil then
+        recv_buff = zbuff.create(1024)
+    end
+    -- 从socket客户端接收数据到缓冲区
+    rec_result = socket.rx(socket_client, recv_buff)
+    if rec_result then
+        -- 取出缓冲区中的数据
+        rec_data = recv_buff:query()
+        -- 如果接收到数据不为空
+        if rec_data ~= nil then
+            -- 记录接收到的数据
+            log.info("收到数据", rec_data)
+        else
+            -- 如果没有接收到数据
+            log.info("无数据返回")
+        end
+    else
+        log.info("数据读取失败")
+    end
+
+end
+--[[
+@function tcp_short_main
+@summary TCP客户端主任务函数
+@usage 实现TCP连接建立、数据发送和连接关闭的完整流程
+]]
+local function tcp_short_main()
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 阻塞等待接收指定taskname为"tcp_short"的任务信息
+    send_data = sys.waitMsg(tcp_short)
+    -- 等待网络适配器就绪
+    log.info("tcp_client_create", "recv IP_READY", socket.dft())
+    -- 创建socket客户端对象
+    socket_client = socket.create(nil, tcp_short)
+    -- 检查socket客户端是否创建成功
+    if socket_client then
+        -- 配置socket客户端
+        if socket.config(socket_client) then
+            -- 连接服务器,超时时间15000ms
+            if libnet.connect(tcp_short, 15000, socket_client, SERVER_ADDR, SERVER_PORT) then
+                -- 发送数据,超时时间15000ms
+                result = libnet.tx(tcp_short, 15000, socket_client, send_data[2])
+                -- 阻塞等待socket.EVENT事件或者3秒钟超时
+                -- 以下三种业务逻辑会发布事件:
+                -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+                -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+                -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+                if tcp_rec then
+                    _ , rec_result = libnet.wait(tcp_short, 3000, socket_client)
+                    -- 有事件触发就执行接收信息动作,超时打印结果
+                    if rec_result then
+                        tcp_rec_short()
+                    else
+                        log.info("tcp_rec_short", "获取信息超时,平台无下发信息。")
+                    end
+                end
+            else
+                result = "connect false" -- tcp客户端连接失败
+            end
+        else
+            result = "config false" -- tcp客户端配置失败
+        end
+    else
+        result = "create false" -- tcp客户端创建失败
+    end
+
+    -- 打印TCP处理结果
+    if result == true then
+        log.info("tcp_short 发送完成")
+    else
+        log.error("tcp_short 发送异常,原因为:", "libnet." .. result .. " error")
+    end
+    -- 关闭连接,超时时间5000ms
+    libnet.close(tcp_short, 5000, socket_client)
+    -- 释放socket客户端资源
+    socket.release(socket_client)
+    socket_client = nil
+    log.info("tcp_short", "close")
+    -- 推送"tcp_short_result"消息,告知外部代码块tco_short模块任务已完成,并携带着发送信息结果和接收到平台的信息。
+    sys.publish("tcp_short_result", result, rec_data)
+end
+
+sys.taskInitEx(tcp_short_main, tcp_short, tcp_short_main_cbfunc)

+ 0 - 76
module/Air780EHM_Air780EHV_Air780EGH/demo/lowpower/ultra_low_power.lua

@@ -1,76 +0,0 @@
-local server_ip = "112.125.89.8" 
-local server_port = 47523 -- 换成自己的
-local period = 3 * 60 * 60 * 1000 -- 3小时唤醒一次
-
-local reason, slp_state = pm.lastReson() -- 获取唤醒原因
-log.info("wakeup state", pm.lastReson())
-local libnet = require "libnet"
-
-local d1Name = "D1_TASK"
-local function netCB(msg)
-    log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
-end
-
-local function testTask(ip, port)
-    local txData
-    if reason == 0 then
-        txData = "normal wakeup"
-    elseif reason == 1 then
-        txData = "timer wakeup"
-    elseif reason == 2 then
-        txData = "pad wakeup"
-    elseif reason == 3 then
-        txData = "uart1 wakeup"
-    end
-    if slp_state > 0 then
-        mobile.flymode(0, false) -- 退出飞行模式,进入psm+前进入飞行模式,唤醒后需要主动退出
-    end
-
-    --gpio.close(32)
-
-    local netc, needBreak
-    local result, param, is_err
-    netc = socket.create(nil, d1Name)
-    socket.debug(netc, false)
-    socket.config(netc) 
-    local retry = 0
-    while retry < 3 do
-        log.info(rtos.meminfo("sys"))
-        result = libnet.waitLink(d1Name, 0, netc)
-        result = libnet.connect(d1Name, 5000, netc, ip, port)
-        if result then
-            log.info("服务器连上了")
-            result, param = libnet.tx(d1Name, 15000, netc, txData)
-            if not result then
-                log.info("服务器断开了", result, param)
-                break
-            else
-                needBreak = true
-            end
-        else
-            log.info("服务器连接失败")
-        end
-        libnet.close(d1Name, 5000, netc)
-        retry = retry + 1
-        if needBreak then
-            break
-        end
-    end
-
-    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
-    
-    -- 配置GPIO以达到最低功耗的目的
-	gpio.close(23) 
-    gpio.close(45) 
-    gpio.close(46) --这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    pm.dtimerStart(3, period) -- 启动深度休眠定时器
-
-    mobile.flymode(0, true) -- 启动飞行模式,规避可能会出现的网络问题
-    pm.power(pm.WORK_MODE, 3) -- 进入极致功耗模式
-
-    sys.wait(15000) -- demo演示唤醒时间是三十分钟,如果15s后模块重启,则说明进入极致功耗模式失败,
-    log.info("进入极致功耗模式失败,尝试重启")
-    rtos.reboot()
-end
-sysplus.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)

+ 127 - 0
module/Air780EPM/demo/lowpower/low_power.lua

@@ -0,0 +1,127 @@
+--[[
+@module  low_power
+@summary 低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否在低功耗模式下使用手机卡、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "low_power"就可以加载运行;
+]] --
+----是否需要插入手机卡,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = false -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI",lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
+
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
+
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    
+    -- 关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
+    pm.power(pm.USB, false)
+end
+
+--[[
+本函数为low_power低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否开启4G、连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function low_power_func()
+    log.info("开始测试低功耗模式功耗。")
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 进入低功耗 MODE 1 模式
+        pm.power(pm.WORK_MODE, 1)
+        -- 判断是否连接TCP平台
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
+        end
+    else
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 如果不开启4G,则进入飞行模式再进入低功耗模式,保持环境干净。
+        mobile.flymode(0, true)
+        pm.power(pm.WORK_MODE, 1)
+    end
+end
+
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
+    end
+end
+
+sys.taskInit(low_power_func)

+ 0 - 88
module/Air780EPM/demo/lowpower/lowpower_dissipation.lua

@@ -1,88 +0,0 @@
-
--- netlab.luatos.com上打开TCP 有测试服务器
-local server_ip = "112.125.89.8"
-local server_port = 43667
-local is_udp = false --用户根据自己实际情况选择
-
---是UDP服务器就赋值为true,是TCP服务器就赋值为flase
---UDP服务器比TCP服务器功耗低
---如果用户对数据的丢包率有极为苛刻的要求,最好选择TCP
-
-local Heartbeat_interval = 1 -- 发送数据的间隔时间,单位分钟
-
--- 数据内容  
-local heart_data = string.rep("1234567890", 10)
-local rxbuf = zbuff.create(8192)
-
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-    if event == socket.LINK then
-    elseif event == socket.ON_LINE then
-        -- 链接上服务器以后发送的第一包数据是 hello,luatos
-        socket.tx(netc, "hello,luatos!")
-
-    elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0, rxbuf:used()), "数据长度", rxbuf:used())
-        end
-        rxbuf:del()
-    elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-    elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
-    end
-end
-
-local function socketTask()
-    local netc = socket.create(nil, netCB) --创建一个链接
-    socket.debug(netc, true)--开启socket层的debug日志,方便寻找问题
-    socket.config(netc, nil, is_udp, nil, 300, 5, 6)  --配置TCP链接的参数,开启保活,防止长时间无数据交互服务器踢掉模块
-    while true do
-        --真正去链接服务器
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        --链接成功后循环发送数据
-        while succ do
-            local Heartbeat_interval = Heartbeat_interval * 60 * 1000
-            sys.wait(Heartbeat_interval)
-            socket.tx(netc, heart_data)
-        end
-        --链接不成功5S重连一次
-        if not succ then
-            log.info("未知错误,5秒后重连")
-            uart.write(1, "未知错误,5秒后重连")
-        else
-            local result, msg = sys.waitUntil("socket_disconnect")
-        end
-        log.info("服务器断开了,5秒后重连")
-        uart.write(1, "服务器断开了,5秒后重连")
-        socket.close(netc)
-        sys.wait(5000)
-    end
-end
-
-function socketDemo()
-
-    --配置GPIO以达到最低功耗的目的
-    gpio.setup(23, nil)
-    gpio.close(33) -- 如果功耗偏高,开始尝试关闭WAKEUPPAD1
-    gpio.close(35) -- 这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
-    pm.power(pm.USB, false)
-
-     --进入低功耗长连接模式
-    pm.power(pm.WORK_MODE, 1)
-
-    -- pm.dtimerStart(0, 120 * 1000)
-    sys.taskInit(socketTask)
-    pm.dtimerStart(0, 120 * 1000)
-end
-
-sys.taskInit(socketDemo)
-

+ 75 - 15
module/Air780EPM/demo/lowpower/main.lua

@@ -1,17 +1,77 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "socket_low_power"
-VERSION = "1.0"
-PRODUCT_KEY = "123" --换成自己的
--- sys库是标配
-_G.sys = require("sys")
-_G.sysplus = require("sysplus")
-log.style(1)
-
--- require "normal" --正常模式
-require "lowpower_dissipation" --低功耗模式
--- require "ultra_low_power" --超低功耗模式(PSM+模式)
-
--- 用户代码已结束---------------------------------------------
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本demo演示的核心功能为:
+三种低功耗模式代码演示和功耗体验
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,持续向平台发送心跳数据。平均功耗:6.6mA
+2、low_power低功耗模式:low_powerr.lua中就是低功耗模式的代码案例,进入低功耗模式后向平台发送心跳包。DTIM1模式平均功耗:1.5mA。DTIM10模式平均功耗380uA。
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,定时唤醒向平台发送心跳包。平均功耗:11uA
+更多说明参考本目录下的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 = "LOWPOWER"
+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)
+
+
+--选择需要体验的功耗模式,注释另外两个代码即可!快捷键Ctrl + /
+-- require "normal_power"
+-- require "low_power"
+require "psm+_power"
+
+
+-- 用户代码已结束---------------------------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 67
module/Air780EPM/demo/lowpower/normal.lua

@@ -1,67 +0,0 @@
-
--- netlab.luatos.com上打开TCP,然后修改IP和端口号,自动回复netlab下发的数据,自收自发测试
-
-local server_ip = "112.125.89.8"
-local server_port = 47523
-
-local rxbuf = zbuff.create(8192)
-
-sys.subscribe("IP_READY", function(ip, adapter)
-    log.info("mobile", "IP_READY", ip, (adapter or -1) == socket.LWIP_GP)
-    sys.publish("net_ready")
-end)
-
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-	if event == socket.LINK then
-	elseif event == socket.ON_LINE then
-        socket.tx(netc, "hello,luatos!")
-	elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0,rxbuf:used()):toHex())
-            log.info("发送", rxbuf:used(), "bytes")
-            socket.tx(netc, rxbuf)
-        end
-        rxbuf:del()
-	elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-	elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
-    end
-end
-
-local function socketTask()
-    sys.waitUntil("net_ready")
-    log.info("联网成功,准备链接服务器")
-    pm.power(pm.WORK_MODE,0) --进入正常模式
-	local netc = socket.create(nil, netCB)
-	socket.debug(netc, true)
-	socket.config(netc, nil, nil, nil, 300, 5, 6)   --开启TCP保活,防止长时间无数据交互被运营商断线
-    while true do
-        log.info("开始链接服务器")
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        if not succ then
-            log.info("未知错误,5秒后重连")
-        else
-            log.info("链接服务器成功")
-            local result, msg = sys.waitUntil("socket_disconnect")
-        end
-        log.info("服务器断开了,5秒后重连")
-        socket.close(netc)
-        log.info(rtos.meminfo("sys"))
-        sys.wait(5000)
-    end
-end
-
-function socketDemo()
-	sys.taskInit(socketTask)
-end
-
-
-socketDemo()

+ 69 - 0
module/Air780EPM/demo/lowpower/normal_power.lua

@@ -0,0 +1,69 @@
+--[[
+@module  normal_power
+@summary 常规模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为常规模式主应用功能模块,核心业务逻辑为:
+1、进入常规模式
+2、判断是否开启4G、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "normal_power"就可以加载运行;
+]] --
+----是否需要开启4G,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = true -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+function normal_power_func()
+    log.info("开始测试常规模式功耗。")
+    -- 将电源模式调整为常规模式。
+    pm.power(pm.WORK_MODE, 0)
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 判断是否连接TCP平台。
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
+        end
+    else
+        -- 开启飞行模式,保持环境干净。
+        mobile.flymode(0, true)
+    end
+end
+
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft())do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:",mobile.status(),"。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
+    end
+end
+
+sys.taskInit(normal_power_func)

+ 144 - 0
module/Air780EPM/demo/lowpower/psm+_power.lua

@@ -0,0 +1,144 @@
+--[[
+@module  psm+_power
+@summary psm+超低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为psm+超低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "psm+_power"就可以加载运行;
+]] --
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = false -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+-- GPIO唤醒函数,用于配置WAKEUP管脚使用
+local function gpio_wakeup()
+    log.info("gpio_wakeup")
+end
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+3、配置三种模块唤醒方式:
+    1)WAKEUP管脚:配置为中断拉低触发唤醒,可用于外部触发唤醒模块;
+    2)dtimerStart:配置休眠定时器,根据预设时间唤醒模块;
+    3)UART1:配置为9600波特率,可用于通过串口发送指令唤醒模块;
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and
+        lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI", lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP0, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP1, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP2, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP3, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP4, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.PWR_KEY, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 配置UART1为9600波特率,可用于唤醒PSM+模式下的模块;
+    -- uart.setup(1,9600)
+
+    -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块;
+    -- pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+
+    -- 从2025年3月份开始的固件版本在pm.power(pm.WORK_MODE, 3)中会自动控制USB和飞行模式,脚本里不需要再手动控制
+    -- pm.power(pm.USB, false)
+    -- mobile.flymode(0, true)
+end
+
+--[[
+本函数为psm+超低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function psm_power_func()
+    log.info("开始测试PSM+模式功耗。")
+    -- 判断是否连接TCP平台。
+    if tcp_mode then
+        -- 导入短连接tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+        require "tcp_short"
+        -- 向指定taskName任务发送一个消息,可以解除指定taskName种的sys.waitMsg的阻塞状态。
+        sys.sendMsg("tcp_short", "data", heart_data)
+        -- 等待短连接TCP功能模块任务完成,获取"tcp_short_result"信息中的发送状态和接收信息
+        local result, send_result, rec_data = sys.waitUntil("tcp_short_result")
+        log.info("信息发送结果:", send_result, "接收到的信息:", rec_data)
+        -- 判断完有没有发送成功后都进入PSM+模式,减少功耗损耗。
+        -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块上传心跳信息。2
+        pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+    end
+    GPIO_setup()
+    -- V2018及以前的版本固件因PSM+模式处理飞行模式逻辑修改,需要增加等待,确保飞行模式执行完成,不然会死机被底层看门狗重启
+    -- V2019及以后的版本固件不需要增加等待
+    sys.wait(500)
+    -- 执行到这条代码后,CPU关机,后续代码不会执行。
+    pm.power(pm.WORK_MODE, 3)
+    -- 下面几行代码实现的功能是:延时等待80秒,然后软件重启;
+    --
+    -- 为什么要写下面三行代码,分以下两种情况介绍:
+    -- 1、当上面执行pm.power(pm.WORK_MODE, 3)时,如果立即成功进入到PSM+模式,则没有机会执行此处的三行代码
+    -- 2、当上面执行pm.power(pm.WORK_MODE, 3)时,如果没有立即成功进入到PSM+模式,则会接着执行此处的三行代码
+    --    在此处最多等待80秒,80秒内,如果成功进入了PSM+模式,则没有机会运行此处的rtos.reboot()软件重启代码
+    --    如果超过了80秒,都没有成功进入PSM+模式,则此处会控制软件重启,
+    --    重启后,根据本demo项目写的业务逻辑,会再次执行进入PSM+模式的逻辑
+    --
+    -- 此处为什么延时等待80秒,是因为从2025年3月份开始,对外发布的内核固件,在脚本中执行pm.power(pm.WORK_MODE, 3)时,
+    -- 如果满足进入PSM+模式的条件,理论上会立即成功进入PSM+模式,
+    -- 虽然如此,为了防止出现不可预知的错误,所以在内核固件中多加了一层保护机制,最长等待75秒钟一定会成功进入PSM+模式
+    -- 所以在此处延时等待80秒,比75秒稍微长个5秒钟,为了让内核固件的这一层保护机制有时间运行,而不必执行此处的软件重启脚本代码
+    -- 减少不必要的重启规避逻辑而带来的多余功耗
+    --
+    -- 这个是设置允许进入PSM+模式的标准操作,必须要与demo保持一致,加入这三行代码
+    sys.wait(80000)
+    log.info("psm_app_task", "进入PSM+失败,重启")
+    rtos.reboot()
+end
+
+sys.taskInit(psm_power_func)

+ 66 - 0
module/Air780EPM/demo/lowpower/readme.md

@@ -0,0 +1,66 @@
+## 演示功能概述
+
+本DEMO演示的核心功能为:
+
+Air780EPM/EHM/EHV/EGH 核心板在常规模式、低功耗模式、PSM+模式的功耗表现;
+
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,5分钟一次向平台发送心跳数据。平均功耗:6mA,无业务待机功耗平均:5mA
+
+2、low_power低功耗模式:low_power.lua中就是低功耗模式的代码案例,进入低功耗模式后5分钟一次向平台发送心跳包。low_power低功耗模式平均功耗:1.5mA,无业务待机功耗:300uA
+
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,5分钟一次唤醒向平台发送心跳包。PSM+极低功耗模式平均功耗:3uA,无业务待机功耗:3uA
+
+## 核心板资料
+
+[Air780EPM/EHM核心板](https://docs.openluat.com/air780epm/product/shouce/)
+
+[Air780EHV核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[Air780EGH核心板](https://docs.openluat.com/air780egh/product/shouce/)
+
+## 演示硬件环境
+
+1、Air780EPM/EHM/EHV/EGH 核心板
+
+2、Air9000P功耗分析仪
+
+3、接线方式
+
+ - Air9000P连接核心板的VBAT和GND接口,低功耗模式需要使用外部供电才能测试;
+
+ - 将USB旁边的拨码开关拨到OFF,断开USB供电;
+
+ - 电脑打开 “ 功耗分析仪 ” 软件,连接Air9000P;
+
+## 演示软件环境
+
+1、Luatools下载调试工具;
+
+2、
+[Air780EPM/EHM核心板](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+[Air780EHV核心板](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[Air780EGH核心板](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、demo脚本代码:
+
+ - tcp_client_main.lua中的上方,修改好 “ SERVER_ADDR ”  “ SERVER_PORT ” 参数,修改为自己测试的TCP服务器IP地址和端口号;
+
+ - low_power.lua \ normal.lua 中的上方,可根据业务需求可以通过参数设置是否开启4G功能、连接TCP服务器功能、心跳数据包,演示对应需求的功耗情况;
+
+ - psm+_power.lua 中调用的是 tcp_short.lua 短连接TCP客户端功能模块,在每次发送信息结束后释放TCP客户端,避免造成TCP客户端资源占用情况,修改好“ SERVER_ADDR ”  “ SERVER_PORT ” 参数  “tcp_rec” 参数 ,即可正常测试。
+
+3、Luatools烧录内核固件和修改后的demo脚本代码;
+
+4、烧录成功后,自动开机运行;
+
+5、USB会漏电,所以烧录成功后先观察Luatools的打印,如果输出 “ D/pm workmode X ( X 是代码中pm.power(pm.WORK_MODE, X)所填的数值 )” 表示已经进入对应的功耗模式,即可拔掉USB,重启核心板,通过观察功耗分析仪的平均功耗值观察是否进入低功耗模式。
+
+## 文档网址
+
+https://e3zt58hesn.feishu.cn/wiki/Cwk2wEcN9if4kUk4iQ7czMhwnug?from=from_copylink

+ 140 - 0
module/Air780EPM/demo/lowpower/tcp_client_main.lua

@@ -0,0 +1,140 @@
+--[[
+@module  tcp_client_main
+@summary tcp client socket主应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket主应用功能模块,核心业务逻辑为:
+1、创建一个tcp client socket,连接server;
+2、处理连接异常,出现异常后执行重连动作;
+3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
+
+本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
+]] local libnet = require "libnet"
+
+-- 加载tcp client socket数据接收功能模块
+local tcp_client_receiver = require "tcp_client_receiver"
+-- 加载tcp client socket数据发送功能模块
+local tcp_client_sender = require "tcp_client_sender"
+
+-- 电脑访问:https://netlab.luatos.com/
+-- 点击 打开TCP 按钮,会创建一个TCP server
+-- 将server的地址和端口赋值给下面这两个变量
+local SERVER_ADDR = "112.125.89.8"
+local SERVER_PORT = 46837
+
+-- tcp_client_main的任务名
+local TASK_NAME = tcp_client_sender.TASK_NAME
+
+-- 处理未识别的消息
+local function tcp_client_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+
+-- tcp client socket的任务处理函数
+local function tcp_client_main_task_func()
+
+    local socket_client
+    local result, para1, para2
+
+    while true do
+        while not socket.adapter(socket.dft()) do
+            -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+            -- 或者等待30秒超时退出阻塞等待状态
+            log.warn("tcp_client_main_task_func", "wait IP_READY")
+            local mobile_result = sys.waitUntil("IP_READY", 30000)
+            if mobile_result then
+                log.info("4G已经连接成功。")
+            else
+                log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+                -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+                mobile.flymode(0, true)
+                mobile.flymode(0, false)
+            end
+        end
+        -- 检测到了IP_READY消息
+        log.info("tcp_client_main_task_func", "recv IP_READY")
+
+        -- 创建socket client对象
+        socket_client = socket.create(nil, TASK_NAME)
+        -- 如果创建socket client对象失败
+        if not socket_client then
+            log.error("tcp_client_main_task_func", "socket.create error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 配置socket client对象为tcp client
+        result = socket.config(socket_client)
+        -- 如果配置失败
+        if not result then
+            log.error("tcp_client_main_task_func", "socket.config error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 连接server
+        result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
+        -- 如果连接server失败
+        if not result then
+            log.error("tcp_client_main_task_func", "libnet.connect error")
+            goto EXCEPTION_PROC
+        end
+
+        log.info("tcp_client_main_task_func", "libnet.connect success")
+
+        -- 数据收发以及网络连接异常事件总处理逻辑
+        while true do
+            -- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
+            -- 如果处理失败,则退出循环
+            if not tcp_client_receiver.proc(socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
+                break
+            end
+
+            -- 数据发送处理
+            -- 如果处理失败,则退出循环
+            if not tcp_client_sender.proc(TASK_NAME, socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
+                break
+            end
+
+            -- 阻塞等待socket.EVENT事件或者15秒钟超时
+            -- 以下三种业务逻辑会发布事件:
+            -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+            -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+            -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+            result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
+            log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
+
+            -- 如果连接异常,则退出循环
+            if not result then
+                log.warn("tcp_client_main_task_func", "connection exception")
+                break
+            end
+        end
+
+        -- 出现异常    
+        ::EXCEPTION_PROC::
+
+        -- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
+        tcp_client_sender.exception_proc()
+
+        -- 如果存在socket client对象
+        if socket_client then
+            -- 关闭socket client连接
+            libnet.close(TASK_NAME, 5000, socket_client)
+
+            -- 释放socket client对象
+            socket.release(socket_client)
+            socket_client = nil
+        end
+
+        -- 5秒后跳转到循环体开始位置,自动发起重连
+        sys.wait(5000)
+    end
+end
+
+-- 创建并且启动一个task
+-- 运行这个task的主函数tcp_client_main_task_func
+sysplus.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)
+

+ 72 - 0
module/Air780EPM/demo/lowpower/tcp_client_receiver.lua

@@ -0,0 +1,72 @@
+--[[
+@module  tcp_client_receiver
+@summary tcp client socket数据接收应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
+从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;
+
+本文件的对外接口有2个:
+1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
+2、sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data):
+   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
+   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
+]]
+
+local tcp_client_receiver = {}
+
+-- socket数据接收缓冲区
+local recv_buff = nil
+
+-- 数据接收应用入口函数
+function tcp_client_receiver.proc(socket_client)
+    -- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
+    if recv_buff==nil then
+        recv_buff = zbuff.create(1024)
+        -- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
+        -- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
+        -- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
+    end
+
+    -- 循环从内核的缓冲区读取接收到的数据
+    -- 如果读取失败,返回false,退出
+    -- 如果读取成功,处理数据,并且继续循环读取
+    -- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
+    while true do
+        -- 从内核的缓冲区中读取数据到recv_buff中
+        -- 如果recv_buff的存储空间不足,会自动扩容
+        local result = socket.rx(socket_client, recv_buff)
+
+        -- 读取数据失败
+        -- 有两种情况:
+        -- 1、recv_buff扩容失败
+        -- 2、socket client和server之间的连接断开
+        if not result then
+            log.error("tcp_client_receiver.proc", "socket.rx error")
+            return false
+        end
+
+        -- 如果读取到了数据, used()就必然大于0, 进行处理
+        if recv_buff:used() > 0 then
+            log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
+
+            -- 读取socket数据接收缓冲区中的数据,赋值给data
+            local data = recv_buff:query()
+
+            -- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
+            sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
+
+            -- 清空socket数据接收缓冲区中的数据
+            recv_buff:del()
+            -- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
+        else
+            break
+        end
+    end
+
+    return true
+end
+
+return tcp_client_receiver

+ 110 - 0
module/Air780EPM/demo/lowpower/tcp_client_sender.lua

@@ -0,0 +1,110 @@
+--[[
+@module  tcp_client_sender
+@summary tcp client socket数据发送应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server;
+3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据;
+4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
+
+本文件的对外接口有3个:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
+   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+   本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
+3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
+]]
+
+local tcp_client_sender = {}
+
+local libnet = require "libnet"
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+}
+data的内容为真正要发送的数据,必须存在;
+func的内容为数据发送结果的用户回调函数,可以不存在
+para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
+]]
+local send_queue = {}
+
+-- tcp_client_main的任务名
+tcp_client_sender.TASK_NAME = "tcp_client_main"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 将原始数据增加前缀,然后插入到发送队列send_queue中
+    table.insert(send_queue, {data="send from "..tag..": "..data, cb=cb})
+    -- 通知tcp_client_main主任务有数据需要发送
+    -- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
+    sysplus.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
+end
+
+-- 数据发送应用逻辑处理入口
+function tcp_client_sender.proc(task_name, socket_client)
+    local send_item
+    local result, buff_full
+
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        -- 取出来第一条数据赋值给send_item
+        -- 同时从队列send_queue中删除这一条数据
+        send_item = table.remove(send_queue,1)
+
+        -- 发送这条数据,超时时间15秒钟
+        result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
+
+        -- 发送失败
+        if not result then
+            log.error("tcp_client_sender.proc", "libnet.tx error")
+
+            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+            if send_item.cb and send_item.cb.func then
+                send_item.cb.func(false, send_item.cb.para)
+            end
+
+            return false
+        end
+
+        -- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
+        if buff_full then
+            log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
+            table.insert(send_queue, 1, send_item)
+            return true
+        end
+
+        log.info("tcp_client_sender.proc", "send success")
+        -- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(true, send_item.cb.para)
+        end
+    end
+
+    return true
+end
+
+-- 数据发送应用逻辑异常处理入口
+function tcp_client_sender.exception_proc()
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        local send_item = table.remove(send_queue,1)
+        -- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(false, send_item.cb.para)
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+return tcp_client_sender

+ 145 - 0
module/Air780EPM/demo/lowpower/tcp_short.lua

@@ -0,0 +1,145 @@
+--[[
+@module  tcp_short
+@summary 短连接TCP客户端功能模块
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件实现了一个短连接TCP客户端,核心功能:
+1. 等待特定消息触发TCP连接
+2. 建立连接后发送队列中的数据
+3. 完成发送后阻塞3秒,等待平台下发的返回信息
+4. 3秒后如果没收到则默认平台无返回信息,随机释放客户端,避免造成过多功耗浪费
+5. 适用于低功耗场景下的周期性数据上报
+本模块功能具备以下对外接口
+1. 可以通过推送消息给"tcp_short",触发消息自动发送。
+2. 通过订阅"tcp_short_close"主题,可以知道tcp_short功能模块任务完成并释放了tcp客户端,并且消息还携带了信息发送状态和平台返回信息
+]] -- 
+-- 服务器地址配置
+local SERVER_ADDR = "112.125.89.8"
+-- 服务器端口配置
+local SERVER_PORT = 44706
+-- 任务名称,用于标识当前TCP客户端任务
+local tcp_short = "tcp_short"
+
+-- 引入网络库,提供TCP通信功能
+local libnet = require "libnet"
+
+local tcp_rec = true
+local recv_buff = nil -- 接收消息缓存区
+local socket_client -- 声明socket客户端对象
+local result, rec_result -- 声明TCP连接任务结果,接收消息结果
+local send_data, rec_data , _  -- 声明发送数据的变量和收到的数据变量
+
+--[[
+@function tcp_short_main_cbfunc
+@summary TCP客户端主任务回调函数
+@param msg 消息内容数组
+@usage 用于处理来自系统的消息通知
+]]
+local function tcp_short_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+--[[
+函数名称: tcp_rec_short
+功能描述: 短连接TCP数据接收处理函数
+          接收TCP下发的数据,接收成功后取出数据给rec_data
+--]]
+local function tcp_rec_short()
+    -- 检查接收缓冲区是否存在,如果不存在则创建1024字节的缓冲区
+    if recv_buff == nil then
+        recv_buff = zbuff.create(1024)
+    end
+    -- 从socket客户端接收数据到缓冲区
+    rec_result = socket.rx(socket_client, recv_buff)
+    if rec_result then
+        -- 取出缓冲区中的数据
+        rec_data = recv_buff:query()
+        -- 如果接收到数据不为空
+        if rec_data ~= nil then
+            -- 记录接收到的数据
+            log.info("收到数据", rec_data)
+        else
+            -- 如果没有接收到数据
+            log.info("无数据返回")
+        end
+    else
+        log.info("数据读取失败")
+    end
+
+end
+--[[
+@function tcp_short_main
+@summary TCP客户端主任务函数
+@usage 实现TCP连接建立、数据发送和连接关闭的完整流程
+]]
+local function tcp_short_main()
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 阻塞等待接收指定taskname为"tcp_short"的任务信息
+    send_data = sys.waitMsg(tcp_short)
+    -- 等待网络适配器就绪
+    log.info("tcp_client_create", "recv IP_READY", socket.dft())
+    -- 创建socket客户端对象
+    socket_client = socket.create(nil, tcp_short)
+    -- 检查socket客户端是否创建成功
+    if socket_client then
+        -- 配置socket客户端
+        if socket.config(socket_client) then
+            -- 连接服务器,超时时间15000ms
+            if libnet.connect(tcp_short, 15000, socket_client, SERVER_ADDR, SERVER_PORT) then
+                -- 发送数据,超时时间15000ms
+                result = libnet.tx(tcp_short, 15000, socket_client, send_data[2])
+                -- 阻塞等待socket.EVENT事件或者3秒钟超时
+                -- 以下三种业务逻辑会发布事件:
+                -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+                -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+                -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+                if tcp_rec then
+                    _ , rec_result = libnet.wait(tcp_short, 3000, socket_client)
+                    -- 有事件触发就执行接收信息动作,超时打印结果
+                    if rec_result then
+                        tcp_rec_short()
+                    else
+                        log.info("tcp_rec_short", "获取信息超时,平台无下发信息。")
+                    end
+                end
+            else
+                result = "connect false" -- tcp客户端连接失败
+            end
+        else
+            result = "config false" -- tcp客户端配置失败
+        end
+    else
+        result = "create false" -- tcp客户端创建失败
+    end
+
+    -- 打印TCP处理结果
+    if result == true then
+        log.info("tcp_short 发送完成")
+    else
+        log.error("tcp_short 发送异常,原因为:", "libnet." .. result .. " error")
+    end
+    -- 关闭连接,超时时间5000ms
+    libnet.close(tcp_short, 5000, socket_client)
+    -- 释放socket客户端资源
+    socket.release(socket_client)
+    socket_client = nil
+    log.info("tcp_short", "close")
+    -- 推送"tcp_short_result"消息,告知外部代码块tco_short模块任务已完成,并携带着发送信息结果和接收到平台的信息。
+    sys.publish("tcp_short_result", result, rec_data)
+end
+
+sys.taskInitEx(tcp_short_main, tcp_short, tcp_short_main_cbfunc)

+ 0 - 76
module/Air780EPM/demo/lowpower/ultra_low_power.lua

@@ -1,76 +0,0 @@
-local server_ip = "112.125.89.8" 
-local server_port = 47523 -- 换成自己的
-local period = 3 * 60 * 60 * 1000 -- 3小时唤醒一次
-
-local reason, slp_state = pm.lastReson() -- 获取唤醒原因
-log.info("wakeup state", pm.lastReson())
-local libnet = require "libnet"
-
-local d1Name = "D1_TASK"
-local function netCB(msg)
-    log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
-end
-
-local function testTask(ip, port)
-    local txData
-    if reason == 0 then
-        txData = "normal wakeup"
-    elseif reason == 1 then
-        txData = "timer wakeup"
-    elseif reason == 2 then
-        txData = "pad wakeup"
-    elseif reason == 3 then
-        txData = "uart1 wakeup"
-    end
-    if slp_state > 0 then
-        mobile.flymode(0, false) -- 退出飞行模式,进入psm+前进入飞行模式,唤醒后需要主动退出
-    end
-
-    --gpio.close(32)
-
-    local netc, needBreak
-    local result, param, is_err
-    netc = socket.create(nil, d1Name)
-    socket.debug(netc, false)
-    socket.config(netc) 
-    local retry = 0
-    while retry < 3 do
-        log.info(rtos.meminfo("sys"))
-        result = libnet.waitLink(d1Name, 0, netc)
-        result = libnet.connect(d1Name, 5000, netc, ip, port)
-        if result then
-            log.info("服务器连上了")
-            result, param = libnet.tx(d1Name, 15000, netc, txData)
-            if not result then
-                log.info("服务器断开了", result, param)
-                break
-            else
-                needBreak = true
-            end
-        else
-            log.info("服务器连接失败")
-        end
-        libnet.close(d1Name, 5000, netc)
-        retry = retry + 1
-        if needBreak then
-            break
-        end
-    end
-
-    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
-    
-    -- 配置GPIO以达到最低功耗的目的
-	gpio.close(23) 
-    gpio.close(45) 
-    gpio.close(46) --这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    pm.dtimerStart(3, period) -- 启动深度休眠定时器
-
-    mobile.flymode(0, true) -- 启动飞行模式,规避可能会出现的网络问题
-    pm.power(pm.WORK_MODE, 3) -- 进入极致功耗模式
-
-    sys.wait(15000) -- demo演示唤醒时间是三十分钟,如果15s后模块重启,则说明进入极致功耗模式失败,
-    log.info("进入极致功耗模式失败,尝试重启")
-    rtos.reboot()
-end
-sysplus.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)

+ 112 - 104
module/Air8000/demo/lowpower/low_power.lua

@@ -1,119 +1,127 @@
+--[[
+@module  low_power
+@summary 低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否在低功耗模式下使用手机卡、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "low_power"就可以加载运行;
+]] --
+----是否需要插入手机卡,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = false -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI",lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
 
--- netlab.luatos.com上打开TCP 有测试服务器
-local server_ip = "112.125.89.8"
-local server_port = 33670
-local is_udp = false --用户根据自己实际情况选择
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
 
---是UDP服务器就赋值为true,是TCP服务器就赋值为flase
---UDP服务器比TCP服务器功耗低
---如果用户对数据的丢包率有极为苛刻的要求,最好选择TCP
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
 
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
 
-local Heartbeat_interval = 5 -- 发送数据的间隔时间,单位分钟
--- 配置GPIO达到最低功耗
-gpio.setup(24, 0) -- 关闭三轴电源
--- 数据内容
-local heart_data = string.rep("1234567890", 10)
-local rxbuf = zbuff.create(8192)
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
 
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-    if event == socket.LINK then
-    elseif event == socket.ON_LINE then
-        -- 链接上服务器以后发送的第一包数据是 hello,luatos
-        socket.tx(netc, "hello,luatos!")
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    
+    -- 关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
+    pm.power(pm.USB, false)
+end
 
-    elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0, rxbuf:used()), "数据长度", rxbuf:used())
+--[[
+本函数为low_power低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否开启4G、连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function low_power_func()
+    log.info("开始测试低功耗模式功耗。")
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 进入低功耗 MODE 1 模式
+        pm.power(pm.WORK_MODE, 1)
+        -- 判断是否连接TCP平台
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
         end
-        rxbuf:del()
-    elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-    elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
+    else
+        -- 关闭这些GPIO可以让功耗效果更好。
+        GPIO_setup()
+        -- 如果不开启4G,则进入飞行模式再进入低功耗模式,保持环境干净。
+        mobile.flymode(0, true)
+        pm.power(pm.WORK_MODE, 1)
     end
 end
 
-local function socketTask()
-    local netc = socket.create(nil, netCB) --创建一个链接
-    socket.debug(netc, true)--开启socket层的debug日志,方便寻找问题
-    socket.config(netc, nil, is_udp, nil, 300, 5, 6)  --配置TCP链接的参数,开启保活,防止长时间无数据交互服务器踢掉模块
-    while true do
-        --真正去链接服务器
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        --链接成功后循环发送数据
-        while succ do
-            local Heartbeat_interval = Heartbeat_interval * 60 * 1000
-            sys.wait(Heartbeat_interval)
-            socket.tx(netc, heart_data)
-        end
-        --链接不成功5S重连一次
-        if not succ then
-            log.info("未知错误,5秒后重连")
-            uart.write(1, "未知错误,5秒后重连")
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
         else
-            local result, msg = sys.waitUntil("socket_disconnect")
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
         end
-        log.info("服务器断开了,5秒后重连")
-        uart.write(1, "服务器断开了,5秒后重连")
-        socket.close(netc)
-        sys.wait(5000)
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
     end
 end
 
-local function sleep_handle()
-    pm.power(pm.WORK_MODE, 1, 1)
-    pm.power(pm.WORK_MODE, 1)
-end
-
--- 收取数据会触发回调, 这里的"receive" 是固定值
-uart.on(1, "receive", function(id, len)
-    local s = ""
-    pm.power(pm.WORK_MODE, 0) -- 进入极致功耗模式
-    repeat
-        s = uart.read(id, 128)
-
-        if #s > 0 then -- #s 是取字符串的长度
-            -- 关于收发hex值,请查阅 https://doc.openluat.com/article/583
-            log.info("uart", "receive", id, #s, s)
-            uart.write(1, "recv:" .. s)
-            -- log.info("uart", "receive", id, #s, s:toHex()) --如果传输二进制/十六进制数据, 部分字符不可见, 不代表没收到
-        end
-    sys.timerStart(sleep_handle,1500)   --  延迟一段时间,不然无法打印日志,如果不考虑打印日志,可以直接进入休眠
-    until s == ""
-end)
-
-function socketDemo()
-    sys.wait(2000)
-    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
-    uart.write(1, "test lowpower")
-    log.info("开始测试低功耗模式")
-    sys.wait(2000)
-    --配置GPIO以达到最低功耗的目的
-    gpio.close(33) -- 如果功耗偏高,开始尝试关闭WAKEUPPAD1
-    gpio.close(35) -- 这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
-    pm.power(pm.USB, false)
-
-    --进入低功耗长连接模式
-    -- WiFi模组进入低功耗模式
-    pm.power(pm.WORK_MODE, 1, 1)
-    -- 同时4G进入低功耗模式
-    pm.power(pm.WORK_MODE, 1)
-    sys.wait(20)
-    -- 暂停airlink通信,进一步降低功耗
-    airlink.pause(1)
-
-    sys.taskInit(socketTask)
-end
-
-sys.taskInit(socketDemo)
-
+sys.taskInit(low_power_func)

+ 75 - 15
module/Air8000/demo/lowpower/main.lua

@@ -1,17 +1,77 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "socket_low_power"
-VERSION = "1.0"
-PRODUCT_KEY = "123" --换成自己的
--- sys库是标配
-_G.sys = require("sys")
-_G.sysplus = require("sysplus")
-log.style(1)
-
--- require "normal" --正常模式
-require "low_power" --低功耗模式
---  require "ultra_low_power" --超低功耗模式(PSM+模式)
-
--- 用户代码已结束---------------------------------------------
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本demo演示的核心功能为:
+三种低功耗模式代码演示和功耗体验
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,持续向平台发送心跳数据。平均功耗:6.6mA
+2、low_power低功耗模式:low_powerr.lua中就是低功耗模式的代码案例,进入低功耗模式后向平台发送心跳包。DTIM1模式平均功耗:1.5mA。DTIM10模式平均功耗380uA。
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,定时唤醒向平台发送心跳包。平均功耗:11uA
+更多说明参考本目录下的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 = "LOWPOWER"
+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)
+
+
+--选择需要体验的功耗模式,注释另外两个代码即可!快捷键Ctrl + /
+-- require "normal_power"
+-- require "low_power"
+require "psm+_power"
+
+
+-- 用户代码已结束---------------------------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 68
module/Air8000/demo/lowpower/normal.lua

@@ -1,68 +0,0 @@
-
--- netlab.luatos.com上打开TCP,然后修改IP和端口号,自动回复netlab下发的数据,自收自发测试
-
-local server_ip = "112.125.89.8"
-local server_port = 47523
-
-local rxbuf = zbuff.create(8192)
--- 配置GPIO达到最低功耗
-gpio.setup(24, 0) -- 关闭三轴电源
-sys.subscribe("IP_READY", function(ip, adapter)
-    log.info("mobile", "IP_READY", ip, (adapter or -1) == socket.LWIP_GP)
-    sys.publish("net_ready")
-end)
-
-local function netCB(netc, event, param)
-    if param ~= 0 then
-        sys.publish("socket_disconnect")
-        return
-    end
-	if event == socket.LINK then
-	elseif event == socket.ON_LINE then
-        socket.tx(netc, "hello,luatos!")
-	elseif event == socket.EVENT then
-        socket.rx(netc, rxbuf)
-        socket.wait(netc)
-        if rxbuf:used() > 0 then
-            log.info("收到", rxbuf:toStr(0,rxbuf:used()):toHex())
-            log.info("发送", rxbuf:used(), "bytes")
-            socket.tx(netc, rxbuf)
-        end
-        rxbuf:del()
-	elseif event == socket.TX_OK then
-        socket.wait(netc)
-        log.info("发送完成")
-	elseif event == socket.CLOSED then
-        sys.publish("socket_disconnect")
-    end
-end
-
-local function socketTask()
-    sys.waitUntil("net_ready")
-    log.info("联网成功,准备链接服务器")
-    pm.power(pm.WORK_MODE,0) --进入正常模式
-	local netc = socket.create(nil, netCB)
-	socket.debug(netc, true)
-	socket.config(netc, nil, nil, nil, 300, 5, 6)   --开启TCP保活,防止长时间无数据交互被运营商断线
-    while true do
-        log.info("开始链接服务器")
-        local succ, result = socket.connect(netc, server_ip, server_port)
-        if not succ then
-            log.info("未知错误,5秒后重连")
-        else
-            log.info("链接服务器成功")
-            local result, msg = sys.waitUntil("socket_disconnect")
-        end
-        log.info("服务器断开了,5秒后重连")
-        socket.close(netc)
-        log.info(rtos.meminfo("sys"))
-        sys.wait(5000)
-    end
-end
-
-function socketDemo()
-	sys.taskInit(socketTask)
-end
-
-
-socketDemo()

+ 69 - 0
module/Air8000/demo/lowpower/normal_power.lua

@@ -0,0 +1,69 @@
+--[[
+@module  normal_power
+@summary 常规模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为常规模式主应用功能模块,核心业务逻辑为:
+1、进入常规模式
+2、判断是否开启4G、连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "normal_power"就可以加载运行;
+]] --
+----是否需要开启4G,测试连接网络状态下功耗--------------------------------------
+local mobile_mode = true -- true 需要   --false 不需要
+-------------------------------------------------------------------------------
+
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = true -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+function normal_power_func()
+    log.info("开始测试常规模式功耗。")
+    -- 将电源模式调整为常规模式。
+    pm.power(pm.WORK_MODE, 0)
+    -- 判断是否开启4G。
+    if mobile_mode then
+        -- 判断是否连接TCP平台。
+        if tcp_mode then
+            -- 导入tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+            require "tcp_client_main"
+            -- 调用发送心跳信息功能函数。
+            send_tcp_heartbeat_func()
+        end
+    else
+        -- 开启飞行模式,保持环境干净。
+        mobile.flymode(0, true)
+    end
+end
+
+-- 定义一个发送心跳信息功能函数。
+function send_tcp_heartbeat_func()
+    -- 通过驻网状态判断4G是否连接成功,不成功则等待成功连接后再开始发送信息。
+    while not socket.adapter(socket.dft())do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:",mobile.status(),"。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 4G驻网后会与基站发送保活心跳。
+    log.info("4G已经连接,开始与基站发送保活心跳")
+    -- 起一个循环定时器,根据预设时间循环定时发送一次消息到TCP服务器。
+    while true do
+        sys.publish("SEND_DATA_REQ", "timer", heart_data)
+        sys.wait(tcp_heartbeat * 60 * 1000)
+    end
+end
+
+sys.taskInit(normal_power_func)

+ 144 - 0
module/Air8000/demo/lowpower/psm+_power.lua

@@ -0,0 +1,144 @@
+--[[
+@module  psm+_power
+@summary psm+超低功耗模式主应用功能模块 
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件为psm+超低功耗模式主应用功能模块,核心业务逻辑为:
+1、进入低功耗模式
+2、判断是否连接TCP服务器和发送平台心跳包
+使用前请根据需要,变更功能变量。条件不同,功耗体现不同。
+本文件没有对外接口,直接在main.lua中require "psm+_power"就可以加载运行;
+]] --
+-----是否需要保持服务器心跳------------------------------------------------------
+local tcp_mode = false -- true 需要连接TCP服务器,设置下方心跳包。    --false 不需要连接TCP服务器,不需要设置心跳包。
+local tcp_heartbeat = 5 -- 常规模式和低功耗模式心跳包,单位(分钟),输入 1 为 一分钟一次心跳包。
+local heart_data = string.rep("1234567890", 3) -- 心跳包数据内容,可自定义。
+-------------------------------------------------------------------------------
+
+-- GPIO唤醒函数,用于配置WAKEUP管脚使用
+local function gpio_wakeup()
+    log.info("gpio_wakeup")
+end
+
+--[[
+本函数为GPIO配置函数,核心业务逻辑为:
+1、关闭Vref管脚,默认拉高,会影响功耗展示,关闭后有效降低功耗。
+2、将所有WAEKUP管脚设置为输入模式内部拉低可以有效防止管脚漏电发生。
+3、配置三种模块唤醒方式:
+    1)WAKEUP管脚:配置为中断拉低触发唤醒,可用于外部触发唤醒模块;
+    2)dtimerStart:配置休眠定时器,根据预设时间唤醒模块;
+    3)UART1:配置为9600波特率,可用于通过串口发送指令唤醒模块;
+本函数属于饱和式管脚配置,调用后可以防止下列管脚的状态异常,导致功耗异常增高,也可以不调用,根据实际情况选择;
+]] --
+function GPIO_setup()
+    local lowpower_module = hmeta.model()
+    -- 判断使用的模组型号,如果为Air8000A/AB/N/U/W,则不允许控制GPIO22和GPIO23
+    -- 在含WIFI功能的Air8000系列模组中,GPIO23为WIFI芯片的供电使能脚,GPIO22为WIFI芯片的通讯脚,不允许控制
+    if lowpower_module ~= "Air8000A" and lowpower_module ~= "Air8000AB" and lowpower_module ~= "Air8000N" and
+        lowpower_module ~= "Air8000U" and lowpower_module ~= "Air8000W" then
+
+        -- 在不含WIFI的Air8000系列,Air780系列,Air700系列模组中GPIO23是Vref参考电压管脚,固件默认拉高,会影响功耗展示,关闭可有效降低功耗。
+        -- Air780EGH/EGG/EGP中,Vref为定位芯片备电使用,在含Gsensor的型号中作为Gsensor的供电使能,关闭后会影响功能使用,需根据实际情况选择是否关闭
+        gpio.setup(23, nil, gpio.PULLDOWN)
+
+        -- gpio.WAKEUP5 = GPIO 22,不需要使用时主动将其关闭可避免漏电风险
+        -- 如测试模块型号为Air780EHV时,需注意如调用了exaudio库,该管脚为外部PA控制脚,不要配置,否则会导致外部PA无法正常工作
+        gpio.setup(gpio.WAKEUP5, nil, gpio.PULLDOWN)
+        -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+        -- gpio.setup(gpio.WAKEUP5, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+        log.info("模组非8000含WIFI", lowpower_module)
+    end
+    -- WAKEUP0专用管脚,无复用,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP0, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP0, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP1 = VBUS,检测USB插入使用,关闭则无法检测是否插入USB,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP1, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP1, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP2 = SIM卡的DET检测脚,用于检测是否插卡,关闭则无法检测是否插卡,不需要使用时主动将其关闭可避免漏电风险
+    gpio.setup(gpio.WAKEUP2, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP2, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP3 = GPIO 20,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EHV时,需注意该管脚内部用于控制Audio Codec芯片ES8311的开关,不要配置,否则会导致Audio Codec芯片ES8311无法正常工作
+    gpio.setup(gpio.WAKEUP3, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP3, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- gpio.WAKEUP4 = GPIO 21,不需要使用时主动将其关闭可避免漏电风险
+    -- 如测试模块型号为Air780EGP/EGG/EGH时,需注意该管脚内部用于控制GNSS定位芯片的开关使能,不要配置,否则会导致GNSS定位芯片无法正常工作
+    gpio.setup(gpio.WAKEUP4, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.WAKEUP4, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 如果硬件上PWR_KEY接地自动开机,可能会有漏电流风险,配置关闭可避免功耗异常,没接地可以不关
+    gpio.setup(gpio.PWR_KEY, nil, gpio.PULLDOWN)
+    -- 可在进入低功耗模式时保持管脚状态,也可以配置为中断拉低触发唤醒,选择对应配置代码即可
+    -- gpio.setup(gpio.PWR_KEY, gpio_wakeup, gpio.PULLUP, gpio.FALLING)
+
+    -- 配置UART1为9600波特率,可用于唤醒PSM+模式下的模块;
+    -- uart.setup(1,9600)
+
+    -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块;
+    -- pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+
+    -- 从2025年3月份开始的固件版本在pm.power(pm.WORK_MODE, 3)中会自动控制USB和飞行模式,脚本里不需要再手动控制
+    -- pm.power(pm.USB, false)
+    -- mobile.flymode(0, true)
+end
+
+--[[
+本函数为psm+超低功耗模式主应用功能函数,核心业务逻辑为:
+1、判断是否连接TCP服务器和发送平台心跳包
+2、配置WAKEUP、USB、PWR_KEY,Vref,减少管脚状态带来的功耗异常情况
+3、进入低功耗模式
+]] --
+function psm_power_func()
+    log.info("开始测试PSM+模式功耗。")
+    -- 判断是否连接TCP平台。
+    if tcp_mode then
+        -- 导入短连接tcp客户端收发功能模块,运行tcp客户端连接,自动处理TCP收发消息。
+        require "tcp_short"
+        -- 向指定taskName任务发送一个消息,可以解除指定taskName种的sys.waitMsg的阻塞状态。
+        sys.sendMsg("tcp_short", "data", heart_data)
+        -- 等待短连接TCP功能模块任务完成,获取"tcp_short_result"信息中的发送状态和接收信息
+        local result, send_result, rec_data = sys.waitUntil("tcp_short_result")
+        log.info("信息发送结果:", send_result, "接收到的信息:", rec_data)
+        -- 判断完有没有发送成功后都进入PSM+模式,减少功耗损耗。
+        -- 配置dtimerStart唤醒定时器,根据预设时间唤醒模块上传心跳信息。2
+        pm.dtimerStart(0, tcp_heartbeat * 60 * 1000)
+    end
+    GPIO_setup()
+    -- V2018及以前的版本固件因PSM+模式处理飞行模式逻辑修改,需要增加等待,确保飞行模式执行完成,不然会死机被底层看门狗重启
+    -- V2019及以后的版本固件不需要增加等待
+    sys.wait(500)
+    -- 执行到这条代码后,CPU关机,后续代码不会执行。
+    pm.power(pm.WORK_MODE, 3)
+    -- 下面几行代码实现的功能是:延时等待80秒,然后软件重启;
+    --
+    -- 为什么要写下面三行代码,分以下两种情况介绍:
+    -- 1、当上面执行pm.power(pm.WORK_MODE, 3)时,如果立即成功进入到PSM+模式,则没有机会执行此处的三行代码
+    -- 2、当上面执行pm.power(pm.WORK_MODE, 3)时,如果没有立即成功进入到PSM+模式,则会接着执行此处的三行代码
+    --    在此处最多等待80秒,80秒内,如果成功进入了PSM+模式,则没有机会运行此处的rtos.reboot()软件重启代码
+    --    如果超过了80秒,都没有成功进入PSM+模式,则此处会控制软件重启,
+    --    重启后,根据本demo项目写的业务逻辑,会再次执行进入PSM+模式的逻辑
+    --
+    -- 此处为什么延时等待80秒,是因为从2025年3月份开始,对外发布的内核固件,在脚本中执行pm.power(pm.WORK_MODE, 3)时,
+    -- 如果满足进入PSM+模式的条件,理论上会立即成功进入PSM+模式,
+    -- 虽然如此,为了防止出现不可预知的错误,所以在内核固件中多加了一层保护机制,最长等待75秒钟一定会成功进入PSM+模式
+    -- 所以在此处延时等待80秒,比75秒稍微长个5秒钟,为了让内核固件的这一层保护机制有时间运行,而不必执行此处的软件重启脚本代码
+    -- 减少不必要的重启规避逻辑而带来的多余功耗
+    --
+    -- 这个是设置允许进入PSM+模式的标准操作,必须要与demo保持一致,加入这三行代码
+    sys.wait(80000)
+    log.info("psm_app_task", "进入PSM+失败,重启")
+    rtos.reboot()
+end
+
+sys.taskInit(psm_power_func)

+ 59 - 0
module/Air8000/demo/lowpower/readme.md

@@ -0,0 +1,59 @@
+## 演示功能概述
+
+本DEMO演示的核心功能为:
+
+Air8000系列 核心板在常规模式、低功耗模式、PSM+模式的功耗表现;
+
+1、normal_power常规模式:normal_power.lua中就是常规模式的代码案例,5分钟一次向平台发送心跳数据。平均功耗:7.1mA,无业务待机功耗平均:6mA
+
+2、low_power低功耗模式:low_power.lua中就是低功耗模式的代码案例,进入低功耗模式后5分钟一次向平台发送心跳包。low_power低功耗模式平均功耗:1.6mA,无业务待机功耗:1.5mA
+
+3、psm+_power低功耗模式:psm+_power.lua中就是PSM+模式的代码案例,5分钟一次唤醒向平台发送心跳包。PSM+极低功耗模式平均功耗:40uA,无业务待机功耗:40uA
+
+## 核心板资料
+
+[Air8000系列核心板](https://docs.openluat.com/air8000/product/shouce/)
+
+
+## 演示硬件环境
+
+1、Air8000系列 核心板
+
+2、Air9000P功耗分析仪
+
+3、接线方式
+
+ - Air9000P连接核心板的VBAT和GND接口,低功耗模式需要使用外部供电才能测试;
+
+ - 将USB旁边的拨码开关拨到OFF,断开USB供电;
+
+ - 电脑打开 “ 功耗分析仪 ” 软件,连接Air9000P;
+
+## 演示软件环境
+
+1、Luatools下载调试工具;
+
+2、
+[Air8000系列核心板](https://docs.openluat.com/air8000/product/shouce/)
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、demo脚本代码:
+
+ - tcp_client_main.lua中的上方,修改好 “ SERVER_ADDR ”  “ SERVER_PORT ” 参数,修改为自己测试的TCP服务器IP地址和端口号;
+
+ - low_power.lua \ normal.lua 中的上方,可根据业务需求可以通过参数设置是否开启4G功能、连接TCP服务器功能、心跳数据包,演示对应需求的功耗情况;
+
+ - psm+_power.lua 中调用的是 tcp_short.lua 短连接TCP客户端功能模块,在每次发送信息结束后释放TCP客户端,避免造成TCP客户端资源占用情况,修改好“ SERVER_ADDR ”  “ SERVER_PORT ” 参数  “tcp_rec” 参数 ,即可正常测试。
+
+3、Luatools烧录内核固件和修改后的demo脚本代码;
+
+4、烧录成功后,自动开机运行;
+
+5、USB会漏电,所以烧录成功后先观察Luatools的打印,如果输出 “ D/pm workmode X ( X 是代码中pm.power(pm.WORK_MODE, X)所填的数值 )” 表示已经进入对应的功耗模式,即可拔掉USB,重启核心板,通过观察功耗分析仪的平均功耗值观察是否进入低功耗模式。
+
+## 文档网址
+
+https://e3zt58hesn.feishu.cn/wiki/Cwk2wEcN9if4kUk4iQ7czMhwnug?from=from_copylink

+ 140 - 0
module/Air8000/demo/lowpower/tcp_client_main.lua

@@ -0,0 +1,140 @@
+--[[
+@module  tcp_client_main
+@summary tcp client socket主应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket主应用功能模块,核心业务逻辑为:
+1、创建一个tcp client socket,连接server;
+2、处理连接异常,出现异常后执行重连动作;
+3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
+
+本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
+]] local libnet = require "libnet"
+
+-- 加载tcp client socket数据接收功能模块
+local tcp_client_receiver = require "tcp_client_receiver"
+-- 加载tcp client socket数据发送功能模块
+local tcp_client_sender = require "tcp_client_sender"
+
+-- 电脑访问:https://netlab.luatos.com/
+-- 点击 打开TCP 按钮,会创建一个TCP server
+-- 将server的地址和端口赋值给下面这两个变量
+local SERVER_ADDR = "112.125.89.8"
+local SERVER_PORT = 46837
+
+-- tcp_client_main的任务名
+local TASK_NAME = tcp_client_sender.TASK_NAME
+
+-- 处理未识别的消息
+local function tcp_client_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+
+-- tcp client socket的任务处理函数
+local function tcp_client_main_task_func()
+
+    local socket_client
+    local result, para1, para2
+
+    while true do
+        while not socket.adapter(socket.dft()) do
+            -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+            -- 或者等待30秒超时退出阻塞等待状态
+            log.warn("tcp_client_main_task_func", "wait IP_READY")
+            local mobile_result = sys.waitUntil("IP_READY", 30000)
+            if mobile_result then
+                log.info("4G已经连接成功。")
+            else
+                log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+                -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+                mobile.flymode(0, true)
+                mobile.flymode(0, false)
+            end
+        end
+        -- 检测到了IP_READY消息
+        log.info("tcp_client_main_task_func", "recv IP_READY")
+
+        -- 创建socket client对象
+        socket_client = socket.create(nil, TASK_NAME)
+        -- 如果创建socket client对象失败
+        if not socket_client then
+            log.error("tcp_client_main_task_func", "socket.create error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 配置socket client对象为tcp client
+        result = socket.config(socket_client)
+        -- 如果配置失败
+        if not result then
+            log.error("tcp_client_main_task_func", "socket.config error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 连接server
+        result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
+        -- 如果连接server失败
+        if not result then
+            log.error("tcp_client_main_task_func", "libnet.connect error")
+            goto EXCEPTION_PROC
+        end
+
+        log.info("tcp_client_main_task_func", "libnet.connect success")
+
+        -- 数据收发以及网络连接异常事件总处理逻辑
+        while true do
+            -- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
+            -- 如果处理失败,则退出循环
+            if not tcp_client_receiver.proc(socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
+                break
+            end
+
+            -- 数据发送处理
+            -- 如果处理失败,则退出循环
+            if not tcp_client_sender.proc(TASK_NAME, socket_client) then
+                log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
+                break
+            end
+
+            -- 阻塞等待socket.EVENT事件或者15秒钟超时
+            -- 以下三种业务逻辑会发布事件:
+            -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+            -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+            -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+            result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
+            log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
+
+            -- 如果连接异常,则退出循环
+            if not result then
+                log.warn("tcp_client_main_task_func", "connection exception")
+                break
+            end
+        end
+
+        -- 出现异常    
+        ::EXCEPTION_PROC::
+
+        -- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
+        tcp_client_sender.exception_proc()
+
+        -- 如果存在socket client对象
+        if socket_client then
+            -- 关闭socket client连接
+            libnet.close(TASK_NAME, 5000, socket_client)
+
+            -- 释放socket client对象
+            socket.release(socket_client)
+            socket_client = nil
+        end
+
+        -- 5秒后跳转到循环体开始位置,自动发起重连
+        sys.wait(5000)
+    end
+end
+
+-- 创建并且启动一个task
+-- 运行这个task的主函数tcp_client_main_task_func
+sysplus.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)
+

+ 72 - 0
module/Air8000/demo/lowpower/tcp_client_receiver.lua

@@ -0,0 +1,72 @@
+--[[
+@module  tcp_client_receiver
+@summary tcp client socket数据接收应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
+从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;
+
+本文件的对外接口有2个:
+1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
+2、sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data):
+   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
+   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
+]]
+
+local tcp_client_receiver = {}
+
+-- socket数据接收缓冲区
+local recv_buff = nil
+
+-- 数据接收应用入口函数
+function tcp_client_receiver.proc(socket_client)
+    -- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
+    if recv_buff==nil then
+        recv_buff = zbuff.create(1024)
+        -- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
+        -- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
+        -- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
+    end
+
+    -- 循环从内核的缓冲区读取接收到的数据
+    -- 如果读取失败,返回false,退出
+    -- 如果读取成功,处理数据,并且继续循环读取
+    -- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
+    while true do
+        -- 从内核的缓冲区中读取数据到recv_buff中
+        -- 如果recv_buff的存储空间不足,会自动扩容
+        local result = socket.rx(socket_client, recv_buff)
+
+        -- 读取数据失败
+        -- 有两种情况:
+        -- 1、recv_buff扩容失败
+        -- 2、socket client和server之间的连接断开
+        if not result then
+            log.error("tcp_client_receiver.proc", "socket.rx error")
+            return false
+        end
+
+        -- 如果读取到了数据, used()就必然大于0, 进行处理
+        if recv_buff:used() > 0 then
+            log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
+
+            -- 读取socket数据接收缓冲区中的数据,赋值给data
+            local data = recv_buff:query()
+
+            -- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
+            sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
+
+            -- 清空socket数据接收缓冲区中的数据
+            recv_buff:del()
+            -- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
+        else
+            break
+        end
+    end
+
+    return true
+end
+
+return tcp_client_receiver

+ 110 - 0
module/Air8000/demo/lowpower/tcp_client_sender.lua

@@ -0,0 +1,110 @@
+--[[
+@module  tcp_client_sender
+@summary tcp client socket数据发送应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server;
+3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据;
+4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
+
+本文件的对外接口有3个:
+1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
+   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+   本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
+3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
+]]
+
+local tcp_client_sender = {}
+
+local libnet = require "libnet"
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+}
+data的内容为真正要发送的数据,必须存在;
+func的内容为数据发送结果的用户回调函数,可以不存在
+para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
+]]
+local send_queue = {}
+
+-- tcp_client_main的任务名
+tcp_client_sender.TASK_NAME = "tcp_client_main"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 将原始数据增加前缀,然后插入到发送队列send_queue中
+    table.insert(send_queue, {data="send from "..tag..": "..data, cb=cb})
+    -- 通知tcp_client_main主任务有数据需要发送
+    -- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
+    sysplus.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
+end
+
+-- 数据发送应用逻辑处理入口
+function tcp_client_sender.proc(task_name, socket_client)
+    local send_item
+    local result, buff_full
+
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        -- 取出来第一条数据赋值给send_item
+        -- 同时从队列send_queue中删除这一条数据
+        send_item = table.remove(send_queue,1)
+
+        -- 发送这条数据,超时时间15秒钟
+        result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
+
+        -- 发送失败
+        if not result then
+            log.error("tcp_client_sender.proc", "libnet.tx error")
+
+            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+            if send_item.cb and send_item.cb.func then
+                send_item.cb.func(false, send_item.cb.para)
+            end
+
+            return false
+        end
+
+        -- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
+        if buff_full then
+            log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
+            table.insert(send_queue, 1, send_item)
+            return true
+        end
+
+        log.info("tcp_client_sender.proc", "send success")
+        -- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(true, send_item.cb.para)
+        end
+    end
+
+    return true
+end
+
+-- 数据发送应用逻辑异常处理入口
+function tcp_client_sender.exception_proc()
+    -- 遍历数据发送队列send_queue
+    while #send_queue>0 do
+        local send_item = table.remove(send_queue,1)
+        -- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if send_item.cb and send_item.cb.func then
+            send_item.cb.func(false, send_item.cb.para)
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和毁掉参数一起publish出去;
+-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+return tcp_client_sender

+ 145 - 0
module/Air8000/demo/lowpower/tcp_short.lua

@@ -0,0 +1,145 @@
+--[[
+@module  tcp_short
+@summary 短连接TCP客户端功能模块
+@version 1.0
+@date    2025.07.17
+@author  陈取德
+@usage
+本文件实现了一个短连接TCP客户端,核心功能:
+1. 等待特定消息触发TCP连接
+2. 建立连接后发送队列中的数据
+3. 完成发送后阻塞3秒,等待平台下发的返回信息
+4. 3秒后如果没收到则默认平台无返回信息,随机释放客户端,避免造成过多功耗浪费
+5. 适用于低功耗场景下的周期性数据上报
+本模块功能具备以下对外接口
+1. 可以通过推送消息给"tcp_short",触发消息自动发送。
+2. 通过订阅"tcp_short_close"主题,可以知道tcp_short功能模块任务完成并释放了tcp客户端,并且消息还携带了信息发送状态和平台返回信息
+]] -- 
+-- 服务器地址配置
+local SERVER_ADDR = "112.125.89.8"
+-- 服务器端口配置
+local SERVER_PORT = 44706
+-- 任务名称,用于标识当前TCP客户端任务
+local tcp_short = "tcp_short"
+
+-- 引入网络库,提供TCP通信功能
+local libnet = require "libnet"
+
+local tcp_rec = true
+local recv_buff = nil -- 接收消息缓存区
+local socket_client -- 声明socket客户端对象
+local result, rec_result -- 声明TCP连接任务结果,接收消息结果
+local send_data, rec_data , _  -- 声明发送数据的变量和收到的数据变量
+
+--[[
+@function tcp_short_main_cbfunc
+@summary TCP客户端主任务回调函数
+@param msg 消息内容数组
+@usage 用于处理来自系统的消息通知
+]]
+local function tcp_short_main_cbfunc(msg)
+    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+--[[
+函数名称: tcp_rec_short
+功能描述: 短连接TCP数据接收处理函数
+          接收TCP下发的数据,接收成功后取出数据给rec_data
+--]]
+local function tcp_rec_short()
+    -- 检查接收缓冲区是否存在,如果不存在则创建1024字节的缓冲区
+    if recv_buff == nil then
+        recv_buff = zbuff.create(1024)
+    end
+    -- 从socket客户端接收数据到缓冲区
+    rec_result = socket.rx(socket_client, recv_buff)
+    if rec_result then
+        -- 取出缓冲区中的数据
+        rec_data = recv_buff:query()
+        -- 如果接收到数据不为空
+        if rec_data ~= nil then
+            -- 记录接收到的数据
+            log.info("收到数据", rec_data)
+        else
+            -- 如果没有接收到数据
+            log.info("无数据返回")
+        end
+    else
+        log.info("数据读取失败")
+    end
+
+end
+--[[
+@function tcp_short_main
+@summary TCP客户端主任务函数
+@usage 实现TCP连接建立、数据发送和连接关闭的完整流程
+]]
+local function tcp_short_main()
+    while not socket.adapter(socket.dft()) do
+        -- 在此处阻塞等待4G连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
+        -- 或者等待30秒超时退出阻塞等待状态
+        log.warn("tcp_client_main_task_func", "wait IP_READY")
+        local mobile_result = sys.waitUntil("IP_READY", 30000)
+        if mobile_result then
+            log.info("4G已经连接成功。")
+        else
+            log.info("SIM卡异常,当前状态:", mobile.status(), "。请检查SIM卡!")
+            -- 30S后网络还没连接成功,开关一下飞行模式,让SIM卡软重启,重新尝试驻网。
+            mobile.flymode(0, true)
+            mobile.flymode(0, false)
+        end
+    end
+    -- 阻塞等待接收指定taskname为"tcp_short"的任务信息
+    send_data = sys.waitMsg(tcp_short)
+    -- 等待网络适配器就绪
+    log.info("tcp_client_create", "recv IP_READY", socket.dft())
+    -- 创建socket客户端对象
+    socket_client = socket.create(nil, tcp_short)
+    -- 检查socket客户端是否创建成功
+    if socket_client then
+        -- 配置socket客户端
+        if socket.config(socket_client) then
+            -- 连接服务器,超时时间15000ms
+            if libnet.connect(tcp_short, 15000, socket_client, SERVER_ADDR, SERVER_PORT) then
+                -- 发送数据,超时时间15000ms
+                result = libnet.tx(tcp_short, 15000, socket_client, send_data[2])
+                -- 阻塞等待socket.EVENT事件或者3秒钟超时
+                -- 以下三种业务逻辑会发布事件:
+                -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
+                -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
+                -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
+                if tcp_rec then
+                    _ , rec_result = libnet.wait(tcp_short, 3000, socket_client)
+                    -- 有事件触发就执行接收信息动作,超时打印结果
+                    if rec_result then
+                        tcp_rec_short()
+                    else
+                        log.info("tcp_rec_short", "获取信息超时,平台无下发信息。")
+                    end
+                end
+            else
+                result = "connect false" -- tcp客户端连接失败
+            end
+        else
+            result = "config false" -- tcp客户端配置失败
+        end
+    else
+        result = "create false" -- tcp客户端创建失败
+    end
+
+    -- 打印TCP处理结果
+    if result == true then
+        log.info("tcp_short 发送完成")
+    else
+        log.error("tcp_short 发送异常,原因为:", "libnet." .. result .. " error")
+    end
+    -- 关闭连接,超时时间5000ms
+    libnet.close(tcp_short, 5000, socket_client)
+    -- 释放socket客户端资源
+    socket.release(socket_client)
+    socket_client = nil
+    log.info("tcp_short", "close")
+    -- 推送"tcp_short_result"消息,告知外部代码块tco_short模块任务已完成,并携带着发送信息结果和接收到平台的信息。
+    sys.publish("tcp_short_result", result, rec_data)
+end
+
+sys.taskInitEx(tcp_short_main, tcp_short, tcp_short_main_cbfunc)

+ 0 - 79
module/Air8000/demo/lowpower/ultra_low_power.lua

@@ -1,79 +0,0 @@
-local server_ip = "112.125.89.8" 
-local server_port = 46153 -- 换成自己的
-local period = 3 * 60 * 60 * 1000 -- 3小时唤醒一次
--- 配置GPIO达到最低功耗
--- gpio.setup(24, 0) -- 关闭三轴电源
-
-local reason, slp_state = pm.lastReson() -- 获取唤醒原因
-log.info("wakeup state", pm.lastReson())
-local libnet = require "libnet"
-
-local d1Name = "D1_TASK"
-local function netCB(msg)
-    log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
-end
-
-local function testTask(ip, port)
-    local txData
-    if reason == 0 then
-        txData = "normal wakeup"
-    elseif reason == 1 then
-        txData = "timer wakeup"
-    elseif reason == 2 then
-        txData = "pad wakeup"
-    elseif reason == 3 then
-        txData = "uart1 wakeup"
-    end
-    if slp_state > 0 then
-        mobile.flymode(0, false) -- 退出飞行模式,进入psm+前进入飞行模式,唤醒后需要主动退出
-    end
-
-    -- gpio.close(32)
-
-    local netc, needBreak
-    local result, param, is_err
-    netc = socket.create(nil, d1Name)
-    socket.debug(netc, false)
-    socket.config(netc) 
-    local retry = 0
-    while retry < 3 do
-        log.info(rtos.meminfo("sys"))
-        result = libnet.waitLink(d1Name, 0, netc)
-        result = libnet.connect(d1Name, 5000, netc, ip, port)
-        if result then
-            log.info("服务器连上了")
-            result, param = libnet.tx(d1Name, 15000, netc, txData)
-            if not result then
-                log.info("服务器断开了", result, param)
-                break
-            else
-                needBreak = true
-            end
-        else
-            log.info("服务器连接失败")
-        end
-        libnet.close(d1Name, 5000, netc)
-        retry = retry + 1
-        if needBreak then   
-            break
-        end
-    end
-
-    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
-
-    -- 配置GPIO以达到最低功耗的目的
-
-
-    gpio.close(45) 
-    gpio.close(46) --这里pwrkey接地才需要,不接地通过按键控制的不需要
-
-    pm.dtimerStart(3, period) -- 启动深度休眠定时器
-
-    mobile.flymode(0, true) -- 启动飞行模式,规避可能会出现的网络问题
-    pm.power(pm.WORK_MODE, 3) -- 进入极致功耗模式
-
-    sys.wait(15000) -- demo演示唤醒时间是三十分钟,如果15s后模块重启,则说明进入极致功耗模式失败,
-    log.info("进入极致功耗模式失败,尝试重启")
-    rtos.reboot()
-end
-sysplus.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)