Переглянути джерело

update:8000 780EXX 780EPM sms demo 更新

wangchengjun 4 місяців тому
батько
коміт
ec457db2ea
26 змінених файлів з 2360 додано та 219 видалено
  1. 64 73
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/main.lua
  2. 33 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_4g.lua
  3. 84 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_eth_spi.lua
  4. 79 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_multiple.lua
  5. 29 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv_device.lua
  6. 214 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/readme.md
  7. 203 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/sms_app.lua
  8. 66 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/sms/sntp_app.lua
  9. 65 73
      module/Air780EPM/demo/sms/main.lua
  10. 33 0
      module/Air780EPM/demo/sms/netdrv/netdrv_4g.lua
  11. 84 0
      module/Air780EPM/demo/sms/netdrv/netdrv_eth_spi.lua
  12. 79 0
      module/Air780EPM/demo/sms/netdrv/netdrv_multiple.lua
  13. 29 0
      module/Air780EPM/demo/sms/netdrv_device.lua
  14. 185 0
      module/Air780EPM/demo/sms/readme.md
  15. 204 0
      module/Air780EPM/demo/sms/sms_app.lua
  16. 66 0
      module/Air780EPM/demo/sms/sntp_app.lua
  17. 65 73
      module/Air8000/demo/sms/main.lua
  18. 33 0
      module/Air8000/demo/sms/netdrv/netdrv_4g.lua
  19. 84 0
      module/Air8000/demo/sms/netdrv/netdrv_eth_spi.lua
  20. 94 0
      module/Air8000/demo/sms/netdrv/netdrv_multiple.lua
  21. 45 0
      module/Air8000/demo/sms/netdrv/netdrv_pc.lua
  22. 49 0
      module/Air8000/demo/sms/netdrv/netdrv_wifi.lua
  23. 37 0
      module/Air8000/demo/sms/netdrv_device.lua
  24. 167 0
      module/Air8000/demo/sms/readme.md
  25. 203 0
      module/Air8000/demo/sms/sms_app.lua
  26. 66 0
      module/Air8000/demo/sms/sntp_app.lua

+ 64 - 73
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/main.lua

@@ -1,87 +1,78 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "smsdemo"
-VERSION = "1.0.0"
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.10.15
+@author  王城钧
+@usage
+1. sms_app:加载短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块
+2. netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+    (1) netdrv_4g:4G网卡
+    (2) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+    (3) netdrv_multiple:支持以上两种网卡,可以配置两种网卡的优先级
+    (4) netdrv_pc: pc模拟器网卡
+3. sntp_app:启动sntp时间同步功能模块,同步网络时间
+]]
 
+
+--[[
+必须定义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 = "sms_app"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
-require "sysplus" -- http库需要这个sysplus
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
-log.info("main", "sms demo")
 
--- 辅助发送http请求, 因为http库需要在task里运行
-function http_post(url, headers, body)
-    sys.taskInit(function()
-        local code, headers, body = http.request("POST", url, headers, body).wait()
-        log.info("resp", code)
-    end)
-end
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-function sms_handler(num, txt)
-    -- num 手机号码
-    -- txt 文本内容
-    log.info("sms", num, txt, txt:toHex())
 
-    -- http演示1, 发json
-    local body = json.encode({phone=num, txt=txt})
-    local headers = {}
-    headers["Content-Type"] = "application/json"
-    log.info("json", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示2, 发表单的
-    headers = {}
-    headers["Content-Type"] = "application/x-www-form-urlencoded"
-    local body = string.format("phone=%s&txt=%s", num:urlEncode(), txt:urlEncode())
-    log.info("params", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示3, 不需要headers,直接发
-    http_post("http://www.luatos.com/api/sms/blackhole", nil, num .. "," .. txt)
-    -- 如需发送到钉钉, 参考 demo/dingding
-    -- 如需发送到飞书, 参考 demo/feishu
-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)
+
 
---------------------------------------------------------------------
--- 接收短信, 支持多种方式, 选一种就可以了
--- 1. 设置回调函数
---sms.setNewSmsCb(sms_handler)
--- 2. 订阅系统消息
---sys.subscribe("SMS_INC", sms_handler)
--- 3. 在task里等着
-sys.taskInit(function()
-    while 1 do
-        local ret, num, txt = sys.waitUntil("SMS_INC", 300000)
-        if num then
-            -- 方案1, 交给自定义函数处理
-            sms_handler(num, txt)
-            -- 方案2, 因为这里是task内, 可以直接调用http.request
-            -- local body = json.encode({phone=num, txt=txt})
-            -- local headers = {}
-            -- headers["Content-Type"] = "application/json"
-            -- log.info("json", body)
-            -- local code, headers, body = http.request("POST", "http://www.luatos.com/api/sms/blackhole", headers, body).wait()
-            -- log.info("resp", code)
-        end
-    end
-end)
+-- 加载sms应用功能模块
+require "sms_app"
 
--------------------------------------------------------------------
--- 发送短信, 直接调用sms.send就行, 是不是task无所谓
-sys.taskInit(function()
-    sys.wait(10000)
-    -- 中移动卡查短信
-    -- sms.send("+8610086", "301")
-    -- 联通卡查话费
-     sms.send("10010", "101")
-end)
+-- 加载网络驱动设备功能模块
+require "netdrv_device"
 
+-- 加载sntp时间同步应用功能模块
+require "sntp_app"
 
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
+-- 启动系统调度(必须放在最后)
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 33 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_4g.lua

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

+ 84 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,84 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是Air8000开发板
+-- GPIO140为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(140, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    local result = spi.setup(
+        1,--spi_id
+        nil,
+        0,--CPHA
+        0,--CPOL
+        8,--数据宽度
+        25600000--,--频率
+        -- spi.MSB,--高低位顺序    可选,默认高位在前
+        -- spi.master,--主模式     可选,默认主
+        -- spi.full--全双工       可选,默认全双工
+    )
+    log.info("netdrv_eth_spi", "spi open result", result)
+    --返回值为0,表示打开成功
+    if result ~= 0 then
+        log.error("netdrv_eth_spi", "spi open error",result)
+        return
+    end
+
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 1, 片选 GPIO12
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=1, cs=12})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 79 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/netdrv/netdrv_multiple.lua

@@ -0,0 +1,79 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air8000开发板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 140,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=1, cs=12}
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

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

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

+ 214 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/readme.md

@@ -0,0 +1,214 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
+
+3、sms_app.lua:短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块; 
+
+4、sntp_app.lua:sntp时间同步功能模块
+
+## 演示功能概述
+
+使用Air780EHV/EHM/EGH 核心板测试sms功能
+
+1、短信发送功能;
+
+2、短信接收功能;
+
+3、netdrv_device:短信通过http转发到企业微信/钉钉/飞书平台时,配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+
+(1) netdrv_4g:4G网卡
+
+(2) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+
+(3) netdrv_multiple:支持以上两种网卡,可以配置两种网卡的优先级
+
+(4) netdrv_pc: pc模拟器网卡
+
+4、NTP时间同步
+
+## 演示硬件环境
+
+[](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV.png)
+
+1、Air780EHV/EHM/EGH 核心板一块+手机sim卡一张:
+
+* sim卡插入开发板的sim卡槽;
+
+* 可选AirETH_1000配件板一块,Air780EXX核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air780EXX核心板 | AirETH_1000配件板 |
+| ------------ | -------------- |
+| 3V3          | 3.3v           |
+| gnd          | gnd            |
+| 86/SPI0CLK   | SCK            |
+| 83/SPI0CS    | CSS            |
+| 84/SPI0MISO  | SDO            |
+| 85/SPI0MOSI  | SDI            |
+| 107/GPIO21   | INT            |
+
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EHV/EHM/EGH 核心板和数据线的硬件接线方式为:
+
+* Air780EHV/EHM/EGH 核心板通过TYPE-C USB口供电;(供电拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[必须使用Air780EHM V2018或者更高版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+[必须使用Air780EHV V2018或者更高版本](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[必须使用Air780EGH V2018或者更高版本](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示核心步骤
+
+1、搭建好硬件环境0
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+* 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+* 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+* 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;
+
+3、修改sms_forward.lua文件中的webhook_feishu和webhook_dingding以及webhook_weixi,如需加密也可以填写对应app的secret。
+
+3、烧录内核固件和sms相关demo成功后,自动开机运行。
+
+4、可以看到代码运行结果如下,不管是在选择什么网卡场景下,基本都是如下情况:
+
+以下是默认使用4G网卡下使用sms demo演示的日志
+
+日志中如果出现以下类似打印则说明短信转发成功:
+
+```
+[2025-11-13 16:43:50.370][000000004.268] I/user.现在可以收发短信
+
+[2025-11-13 16:43:50.375][000000004.268] I/user.mobile.number(id) =  +8617374070417
+
+[2025-11-13 16:43:50.378][000000004.269] I/user.mobile.iccid(id) =  89861123045773964016
+
+[2025-11-13 16:43:50.383][000000004.269] I/user.mobile.simid(id) =  0
+
+[2025-11-13 16:43:50.389][000000004.270] I/user.mobile.imsi(index) =  460115188098492
+
+[2025-11-13 16:43:50.393][000000004.270] D/sms pdu len 18
+
+[2025-11-13 16:43:50.396][000000004.272] I/user.发送查询短信 这是第1次发送  发送结果: 成功
+
+[2025-11-13 16:43:50.401][000000004.272] I/user.等待10分钟
+
+[2025-11-13 16:43:50.764][000000004.689] luat_sms_proc 1239:[DIO 1239]: CMI_SMS_SEND_MSG_CNF is in
+
+[2025-11-13 16:43:50.771][000000004.689] D/sms long sms callback seqNum = 1
+
+[2025-11-13 16:43:50.779][000000004.691] I/user.sms send result true
+
+[2025-11-13 16:43:51.983][000000005.903] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:51.990][000000005.904] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:51.994][000000005.904] I/sms long-sms, wait more frags 2/3
+
+[2025-11-13 16:43:52.312][000000006.234] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:52.318][000000006.235] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:52.323][000000006.235] I/sms long-sms, wait more frags 1/3
+
+[2025-11-13 16:43:52.561][000000006.482] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:52.568][000000006.482] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:52.578][000000006.483] I/sms long-sms, wait more frags 2/3
+
+[2025-11-13 16:43:52.828][000000006.746] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:52.838][000000006.746] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:52.849][000000006.747] I/sms long-sms is ok
+
+[2025-11-13 16:43:52.861][000000006.749] I/user.收到来自短信: 10001
+
+[2025-11-13 16:43:52.869][000000006.749] I/user.num是 10001
+
+[2025-11-13 16:43:52.877][000000006.750] I/user.收到来自10001的短信:尊敬的用户:截至11月13日16时42分,您的账户使用情况如下:
+本月消费:15.60元
+可用余额:21.84元
+仅供参考,实际费用以出账账单为准。更多详情点击:http://a.189.cn/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https://qy.k189.cn/hfcx?source=dt
+
+[2025-11-13 16:43:52.882][000000006.750] I/user.当前网络 true 1
+
+[2025-11-13 16:43:52.891][000000006.751] I/user.转发到飞书
+
+[2025-11-13 16:43:52.895][000000006.752] I/user.timestamp 1763023432
+
+[2025-11-13 16:43:52.899][000000006.753] I/user.sign QHEIo55ZRHLWyEuYuZ+7Z1wXzNWOcameWshNHBMetrM=
+
+[2025-11-13 16:43:52.902][000000006.753] I/user.url https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a
+
+[2025-11-13 16:43:52.909][000000006.755] I/user.feishu 
+
+[2025-11-13 16:43:52.913][000000006.755] {"content":{"text":"我的id是nil,Thu Nov 13 16:43:52 2025,Air780EGH,    10001发来短信,内容是:尊敬的用户:截至11月13日16时42分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"sign":"QHEIo55ZRHLWyEuYuZ+7Z1wXzNWOcameWshNHBMetrM=","msg_type":"text","timestamp":"1763023432"}
+
+[2025-11-13 16:43:52.918][000000006.761] dns_run 676:open.feishu.cn state 0 id 2 ipv6 0 use dns server2, try 0
+
+[2025-11-13 16:43:52.923][000000006.796] dns_run 693:dns all done ,now stop
+
+[2025-11-13 16:43:53.405][000000007.319] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:53.411][000000007.322] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:53.415][000000007.322] I/sms long-sms, wait more frags 1/3
+
+[2025-11-13 16:43:53.687][000000007.612] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 16:43:53.695][000000007.613] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 16:43:53.705][000000007.613] I/sms long-sms is ok
+
+[2025-11-13 16:43:54.072][000000007.988] I/user.飞书机器人 success 200 
+
+[2025-11-13 16:43:54.080][000000007.989] {"Request-Id":"1c222dde-8b17-4fda-9abf-2991768ba2ec","X-Lgw-Dst-Svc":"EQwbqA8xDu2dP1kwlU7XzoUdaYWcyx65Bd7EV5Cfg7FDWagbqvSwL6i6ZIZQuk9SPKRp-TLw1YUlWWI1x-qDMK3lcIz3tQMtsb2sNiaoXQANO6idlDedangOCOFppmjl0Vy5medc5dgm3Uvb-DgVlhY97GRJcGcNvC6P_0OcwcSKaS3XH4lhk-viLlzNxzMtJ6jZm7jMW6WDekreEvsQQ6HWqQgpKd0JICazwOIFu96PjqOCkCPiVUUljiIo0UCjP-shVTy85ONU6hMNl_HTYqEQDtSn4k3bchCFVzf7IkrMJ9dQWLR3Pr2KdmtbQfON0SK_a2wWbbp7Bm5MjuhBnVUgauLLrBqDGP25qNzriWz9nX8=","Content-Length":"77","Via":"vcache4.cn1402[378,0]","X-Timestamp":"1763023434.054","x-tt-trace-id":"00-2511131643537EAC40147A978E68587F-67C8D1137A67669F-00","Date":"Thu, 13 Nov 2025 08:43:54 GMT","x-tt-trace-host":"01d37834a09535440d46e83bd3a0cfdf5fc40094e9cf88383df3976d2ca0bd9a896bc5e0b884a8c8636ca2a8b49cd94af7f8fd8c45a543446d0c891f535407f62ea62bc2a07250746a924b3bc7dd92abd898d4600d8b4d6c28fb83755992837c5f","X-Tt-Logid":"202511131643537EAC40147A978E68587F","Server":"Tengine","server-timing":"cdn-cache;desc=MISS,edge;dur=0,origin;dur=378","Tt_stable":"1","x-tt-trace-tag":"id=03;cdn-cache=miss;type=dyn","Content-Type":"application\/json","X-Request-Id":"1c222dde-8b17-4fda-9abf-2991768ba2ec","Timing-Allow-Origin":"*","Connection":"keep-alive","EagleId":"65597d2f17630234336914604e"}
+
+[2025-11-13 16:43:54.096][000000007.989]  {"StatusCode":0,"StatusMessage":"success","code":0,"data":{},"msg":"success"}
+
+[2025-11-13 16:43:55.075][000000008.989] I/user.转发到钉钉
+
+[2025-11-13 16:43:55.077][000000008.991] I/user.timestamp 1763023434000
+
+[2025-11-13 16:43:55.077][000000008.992] I/user.sign l8hDqbTRGULV7bSavPYMaTxmaM59WKmcbifRHwNxzzw%3D
+
+[2025-11-13 16:43:55.078][000000008.992] I/user.url https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b&timestamp=1763023434000&sign=l8hDqbTRGULV7bSavPYMaTxmaM59WKmcbifRHwNxzzw%3D
+
+[2025-11-13 16:43:55.079][000000008.993] I/user.dingding {"text":{"content":"我的id是nil,Thu Nov 13 16:43:54 2025,Air780EGH,    10001发来短信,内容是:尊敬的用户:截至11月13日16时42分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+
+[2025-11-13 16:43:55.079][000000008.995] dns_run 676:oapi.dingtalk.com state 0 id 3 ipv6 0 use dns server2, try 0
+
+[2025-11-13 16:43:55.120][000000009.033] dns_run 693:dns all done ,now stop
+
+[2025-11-13 16:43:55.847][000000009.762] I/user.钉钉机器人 success 200 {"Cache-Control":"no-cache","Date":"Thu, 13 Nov 2025 08:43:55 GMT","Connection":"keep-alive","Server":"DingTalk\/1.0.0","Content-Length":"27","Content-Type":"application\/json"} {"errcode":0,"errmsg":"ok"}
+
+[2025-11-13 16:43:56.848][000000010.763] I/user.转发到微信
+
+[2025-11-13 16:43:56.854][000000010.764] I/user.timestamp 1763023436000
+
+[2025-11-13 16:43:56.856][000000010.764] I/user.url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d&timestamp=1763023436000
+
+[2025-11-13 16:43:56.859][000000010.765] I/user.weixin {"text":{"content":"我的id是nil,Thu Nov 13 16:43:56 2025,Air780EGH,    10001发来短信,内容是:尊敬的用户:截至11月13日16时42分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+
+[2025-11-13 16:43:56.862][000000010.767] dns_run 676:qyapi.weixin.qq.com state 0 id 4 ipv6 0 use dns server2, try 0
+
+[2025-11-13 16:43:56.895][000000010.818] dns_run 693:dns all done ,now stop
+
+[2025-11-13 16:43:57.681][000000011.601] I/user.企业微信机器人 success 200 {"Content-Type":"application\/json; charset=UTF-8","Server":"nginx","Error-Code":"0","Date":"Thu, 13 Nov 2025 08:43:57 GMT","Connection":"keep-alive","Content-Length":"27","Error-Msg":"ok","X-W-No":"5"} {"errcode":0,"errmsg":"ok"}
+
+
+
+```

+ 203 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/sms_app.lua

@@ -0,0 +1,203 @@
+--[[
+@module  sms_forward
+@summary 短信信息转发驱动模块
+@version 1.0
+@date    2025.10.15
+@author  王城钧
+@usage
+本文件为来电信息转发驱动模块,核心业务逻辑为:
+1、配置飞书,钉钉,企业微信机器人的webhook和secret(加签)。
+2、send_sms(),发送短信的功能函数,等待CC_IND消息后,手机卡可以进行收发短信。
+3、receive_sms(),接收短信处理的功能函数,收到短信后获取来信号码和短信内容,通过回调函数sms_handler(num, txt)转发到指定的机器人。
+
+本文件没有对外接口,直接在main.lua中require "sms_forward"就可以加载运行;
+]]
+
+-- webhook_feishu和secret_feishu要换成你自己机器人的值
+-- webhook_feishu是钉钉分配给机器人的URL
+-- secret_feishu是选取 "加签", 自动生成的密钥
+-- 下面的给一个测试群发消息, 随时可能关掉, 请换成你自己的值
+local webhook_feishu = "https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a"
+local secret_feishu = "qlf8UXrJc7RYtJLx77jRVh"
+
+local webhook_dingding =
+"https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b"
+local secret_dingding = "SEC1bec4c6416b14c945806fa658840c7fbc64c3257aacd0c72f6cad5d22e3d29a4"
+
+--local webhook_weixin = "https://work.weixin.qq.com/wework_admin/common/openBotProfile/24caa08b3a985454055047454d883fc98f"
+local webhook_weixin = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d"
+-- 飞书关于机器人的文档 https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
+
+--1.功能函数:短信转发到飞书
+local function feishu_post_sms(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    -- LuatOS的时间戳只到秒,飞书也只需要秒
+    local timestamp = tostring(os.time())
+    local sign = crypto.hmac_sha256("", timestamp .. "\n" .. secret_feishu):fromHex():toBase64()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    -- 注意, 这里的参数跟钉钉不同, 钉钉有个access_token参数, 飞书没有
+    local url = webhook_feishu
+    log.info("url", url)
+    -- json格式也需要按飞书的来
+    local data = { msg_type = "text" }
+    data["timestamp"] = timestamp
+    data["sign"] = sign
+    -- text就是要发送的文本内容, 其他格式按飞书的要求拼接table就好了
+    local text = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["content"] = { text = text }
+    local rbody = (json.encode(data))
+    log.info("feishu", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, rbody, { timeout = 5000 }).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("飞书机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--2.功能函数:短信转发到钉钉
+local function dingding_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+    -- LuatOS的时间戳只到秒,但钉钉需要毫秒,补3个零
+    local timestamp = tostring(os.time()) .. "000"
+    local sign = crypto.hmac_sha256(timestamp .. "\n" .. secret_dingding, secret_dingding):fromHex():toBase64()
+        :urlEncode()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    local url = webhook_dingding .. "&timestamp=" .. timestamp .. "&sign=" .. sign
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("dingding", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("钉钉机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--3.功能函数:短信转发到企业微信
+local function weixin_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    local timestamp = tostring(os.time()) .. "000"
+    log.info("timestamp", timestamp)
+    local url = webhook_weixin .. "&timestamp=" .. timestamp
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("weixin", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("企业微信机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--4.功能函数:接收短信的回调函数
+local function sms_handler(num, txt)
+    -- num 给我发短信的手机号码
+    -- txt 收到的短信文本内容
+
+    log.info("转发到飞书")
+    feishu_post_sms(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到钉钉")
+    dingding_post(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到微信")
+    weixin_post(num, txt)
+end
+
+--------------------------------------------------------------------
+--5. 功能函数:接收短信
+local function receive_sms()
+    while 1 do
+        local ret, num, txt = sys.waitUntil("SMS_INC", 30000)
+        log.info("收到来自短信:", num)
+        if num then
+            log.info("num是", num)
+            log.info("收到来自" .. num .. "的短信:" .. txt)
+
+            --local isReady1, index1 = socket.adapter()
+            log.info("当前网络", socket.adapter())
+
+            sms_handler(num, txt)
+        end
+    end
+end
+
+-------------------------------------------------------------------
+-- 6.功能函数:发送短信, 直接调用sms.send就行, 是不是task无所谓
+local function send_sms()
+    --系统消息CC_IND到了才能收发短信
+    --按照规范的做法,这里应该等待"SMS_READY"消息,
+    --目前内核固件正在开发支持"SMS_READY"消息功能,
+    --等开发好了之后,再使用"SMS_READY"消息,
+    --当前阶段,先使用"CC_IND"替代
+    sys.waitUntil("CC_IND")
+    log.info("发送短信前wait CC_IND")
+    local cont = 1
+    log.info("开始发短信")
+    while 1 do
+        log.info("现在可以收发短信")
+
+        --获取本机号码,如果卡商没写入会返回nil
+        log.info("mobile.number(id) = ", mobile.number())
+        --获取本机iccid,如果卡商没写入会返回nil
+        log.info("mobile.iccid(id) = ", mobile.iccid())
+        --获取本机simid,如果卡商没写入会返回nil
+        log.info("mobile.simid(id) = ", mobile.simid())
+        --获取本机imsi,如果卡商没写入会返回nil
+        log.info("mobile.imsi(index) = ", mobile.imsi())
+        -- 注意:以下查话费的三行代码只需根据自己卡的运营商打开其一即可,其余两行关闭,不要全部打开
+        -- 电信卡查话费
+        local result = sms.send("10001", "102")
+        -- 中移动卡查短信
+        -- local result = sms.send("10086", "301")
+        -- 联通卡查话费
+        -- local result = sms.send("10010", "101")
+        -- 注意:V2018及更高版本固件才有"SMS_SENT"系统消息
+        if result then
+            local wait_msg, success  = sys.waitUntil("SMS_SENT", 10000)
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:", wait_msg and (success and "成功" or "失败") or "超时")
+        else
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:同步发送失败")
+        end
+        log.info("等待10分钟")
+        cont = cont + 1
+        sys.wait(10 * 60 * 1000)
+    end
+end
+
+
+--发送短信
+sys.taskInit(send_sms)
+--接收短信
+sys.taskInit(receive_sms)

+ 66 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/sms/sntp_app.lua

@@ -0,0 +1,66 @@
+--[[
+@module  sntp_app
+@summary sntp时间同步应用功能模块 
+@version 1.0
+@date    2025.10.29
+@author  王城钧
+@usage
+本文件为sntp时间同步应用功能模块,核心业务逻辑为:
+1、连接ntp服务器进行时间同步;
+2、如果同步成功,1小时之后重新发起同步动作;
+3、如果同步失败,10秒钟之后重新发起同步动作;
+
+本文件没有对外接口,直接在其他应用功能模块中require "sntp_app"就可以加载运行;
+]]
+
+-- sntp时间同步的任务处理函数
+local function sntp_task_func() 
+
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("sntp_task_func", "wait IP_READY", socket.dft())
+            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+            -- 或者等待1秒超时退出阻塞等待状态;
+            -- 注意:此处的1000毫秒超时不要修改的更长;
+            -- 因为当使用libnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+            -- 当libnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+            sys.waitUntil("IP_READY", 1000)
+        end
+
+        -- 检测到了IP_READY消息
+        log.warn("sntp_task_func", "recv IP_READY")
+
+        -- 发起ntp时间同步动作
+        socket.sntp()
+
+        -- 等待ntp时间同步结果,30秒超时失败,通常只需要几百毫秒就能成功
+        local ret = sys.waitUntil("NTP_UPDATE", 30000)
+
+        --同步成功
+        if ret then
+            -- 以下是获取/打印时间的演示,注意时区问题
+            log.info("sntp_task_func", "时间同步成功", "本地时间", os.date())
+            log.info("sntp_task_func", "时间同步成功", "UTC时间", os.date("!%c"))
+            log.info("sntp_task_func", "时间同步成功", "RTC时钟(UTC时间)", json.encode(rtc.get()))
+            log.info("sntp_task_func", "时间同步成功", "本地时间戳", os.time())
+            local t = os.date("*t")
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date() json格式", json.encode(t))
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date(os.time())", os.time(t))
+
+            -- 正常使用, 一小时一次, 已经足够了, 甚至1天一次也可以
+            sys.wait(3600000) 
+        --同步失败
+        else
+            log.info("sntp_task_func", "时间同步失败")
+            -- 10秒后重新发起同步动作
+            sys.wait(10000) 
+        end
+    end
+end
+
+--创建并且启动一个task
+--运行这个task的主函数sntp_task_func
+sys.taskInit(sntp_task_func)
+

+ 65 - 73
module/Air780EPM/demo/sms/main.lua

@@ -1,87 +1,79 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "smsdemo"
-VERSION = "1.0.0"
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.10.15
+@author  王城钧
+@usage
+1. sms_app:加载短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块
+2. netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+    (1) netdrv_4g:4G网卡
+    (2) netdrv_wifi:WIFI STA网卡
+    (3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+    (4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+    (5) netdrv_pc: pc模拟器网卡
+3. sntp_app:启动sntp时间同步功能模块,同步网络时间
+]]
 
+
+--[[
+必须定义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 = "sms_app"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
-require "sysplus" -- http库需要这个sysplus
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
-log.info("main", "sms demo")
 
--- 辅助发送http请求, 因为http库需要在task里运行
-function http_post(url, headers, body)
-    sys.taskInit(function()
-        local code, headers, body = http.request("POST", url, headers, body).wait()
-        log.info("resp", code)
-    end)
-end
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-function sms_handler(num, txt)
-    -- num 手机号码
-    -- txt 文本内容
-    log.info("sms", num, txt, txt:toHex())
 
-    -- http演示1, 发json
-    local body = json.encode({phone=num, txt=txt})
-    local headers = {}
-    headers["Content-Type"] = "application/json"
-    log.info("json", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示2, 发表单的
-    headers = {}
-    headers["Content-Type"] = "application/x-www-form-urlencoded"
-    local body = string.format("phone=%s&txt=%s", num:urlEncode(), txt:urlEncode())
-    log.info("params", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示3, 不需要headers,直接发
-    http_post("http://www.luatos.com/api/sms/blackhole", nil, num .. "," .. txt)
-    -- 如需发送到钉钉, 参考 demo/dingding
-    -- 如需发送到飞书, 参考 demo/feishu
-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)
+
 
---------------------------------------------------------------------
--- 接收短信, 支持多种方式, 选一种就可以了
--- 1. 设置回调函数
---sms.setNewSmsCb(sms_handler)
--- 2. 订阅系统消息
---sys.subscribe("SMS_INC", sms_handler)
--- 3. 在task里等着
-sys.taskInit(function()
-    while 1 do
-        local ret, num, txt = sys.waitUntil("SMS_INC", 300000)
-        if num then
-            -- 方案1, 交给自定义函数处理
-            sms_handler(num, txt)
-            -- 方案2, 因为这里是task内, 可以直接调用http.request
-            -- local body = json.encode({phone=num, txt=txt})
-            -- local headers = {}
-            -- headers["Content-Type"] = "application/json"
-            -- log.info("json", body)
-            -- local code, headers, body = http.request("POST", "http://www.luatos.com/api/sms/blackhole", headers, body).wait()
-            -- log.info("resp", code)
-        end
-    end
-end)
+-- 加载sms应用功能模块
+require "sms_app"
 
--------------------------------------------------------------------
--- 发送短信, 直接调用sms.send就行, 是不是task无所谓
-sys.taskInit(function()
-    sys.wait(10000)
-    -- 中移动卡查短信
-    -- sms.send("+8610086", "301")
-    -- 联通卡查话费
-     sms.send("10010", "101")
-end)
+-- 加载网络驱动设备功能模块
+require "netdrv_device"
 
+-- 加载sntp时间同步应用功能模块
+require "sntp_app"
 
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
+-- 启动系统调度(必须放在最后)
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 33 - 0
module/Air780EPM/demo/sms/netdrv/netdrv_4g.lua

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

+ 84 - 0
module/Air780EPM/demo/sms/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,84 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是Air8000开发板
+-- GPIO140为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(140, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    local result = spi.setup(
+        1,--spi_id
+        nil,
+        0,--CPHA
+        0,--CPOL
+        8,--数据宽度
+        25600000--,--频率
+        -- spi.MSB,--高低位顺序    可选,默认高位在前
+        -- spi.master,--主模式     可选,默认主
+        -- spi.full--全双工       可选,默认全双工
+    )
+    log.info("netdrv_eth_spi", "spi open result", result)
+    --返回值为0,表示打开成功
+    if result ~= 0 then
+        log.error("netdrv_eth_spi", "spi open error",result)
+        return
+    end
+
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 1, 片选 GPIO12
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=1, cs=12})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 79 - 0
module/Air780EPM/demo/sms/netdrv/netdrv_multiple.lua

@@ -0,0 +1,79 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air8000开发板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 140,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=1, cs=12}
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 29 - 0
module/Air780EPM/demo/sms/netdrv_device.lua

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

+ 185 - 0
module/Air780EPM/demo/sms/readme.md

@@ -0,0 +1,185 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
+
+3、sms_app.lua:短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块; 
+
+4、sntp_app.lua:sntp时间同步功能模块
+
+## 演示功能概述
+
+使用Air780EPM 开发板测试sms功能
+
+1、短信发送功能;
+
+2、短信接收功能;
+
+3、netdrv_device:短信通过http转发到企业微信/钉钉/飞书平台时,配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+
+(1) netdrv_4g:4G网卡
+
+(2) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+
+(3) netdrv_multiple:支持以上两种网卡,可以配置两种网卡的优先级
+
+(4) netdrv_pc: pc模拟器网卡
+
+4、NTP时间同步
+
+## 演示硬件环境
+
+![img](https://docs.openluat.com/air780epm/luatos/app/driver/eth/image/RFSvb75NRoEWqYxfCRVcVrOKnsf.jpg)
+
+1、Air780EPM V1.3版本开发板一块+可上网的sim卡一张+4g天线一根+网线一根:
+
+- sim卡插入开发板的sim卡槽
+
+- 天线装到开发板上
+
+- 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EPM V1.3版本开发板和数据线的硬件接线方式为:
+
+- Air780EPM V1.3版本开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+- TYPE-C USB数据线直接插到开发板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[必须使用Air780EPM V2018或者更高版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2018-106固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+* 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+* 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+* 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;
+
+* 如果需要pc模拟器网卡,打开require "netdrv_pc",其余注释掉;
+
+3、修改sms_forward.lua文件中的webhook_feishu和webhook_dingding以及webhook_weixi,如需加密也可以填写对应app的secret。
+
+3、烧录内核固件和sms相关demo成功后,自动开机运行。
+
+4、可以看到代码运行结果如下,不管是在选择什么网卡场景下,基本都是如下情况:
+
+以下是默认使用4G网卡下使用sms demo演示的日志
+
+日志中如果出现以下类似打印则说明短信转发成功:
+
+```
+[2025-11-13 17:06:24.610][000000007.198] I/user.现在可以收发短信
+
+[2025-11-13 17:06:24.618][000000007.199] I/user.mobile.number(id) =  +8617374070417
+
+[2025-11-13 17:06:24.621][000000007.199] I/user.mobile.iccid(id) =  89861123045773964016
+
+[2025-11-13 17:06:24.627][000000007.199] I/user.mobile.simid(id) =  0
+
+[2025-11-13 17:06:24.631][000000007.200] I/user.mobile.imsi(index) =  460115188098492
+
+[2025-11-13 17:06:24.636][000000007.200] D/sms pdu len 18
+
+[2025-11-13 17:06:24.639][000000007.202] I/user.发送查询短信 这是第1次发送  发送结果: 成功
+
+[2025-11-13 17:06:24.644][000000007.202] I/user.等待10分钟
+
+[2025-11-13 17:06:24.790][000000007.384] luat_sms_proc 1239:[DIO 1239]: CMI_SMS_SEND_MSG_CNF is in
+
+[2025-11-13 17:06:24.797][000000007.385] D/sms long sms callback seqNum = 1
+
+[2025-11-13 17:06:24.801][000000007.386] I/user.sms send result true
+
+[2025-11-13 17:06:26.549][000000009.138] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 17:06:26.572][000000009.138] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 17:06:26.587][000000009.139] I/sms long-sms, wait more frags 1/3
+
+[2025-11-13 17:06:26.653][000000009.245] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 17:06:26.658][000000009.246] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 17:06:26.664][000000009.246] I/sms long-sms, wait more frags 2/3
+
+[2025-11-13 17:06:26.861][000000009.455] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-11-13 17:06:26.889][000000009.455] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-11-13 17:06:26.908][000000009.455] I/sms long-sms is ok
+
+[2025-11-13 17:06:26.924][000000009.457] I/user.收到来自短信: 10001
+
+[2025-11-13 17:06:26.942][000000009.457] I/user.num是 10001
+
+[2025-11-13 17:06:26.956][000000009.458] I/user.收到来自10001的短信:尊敬的用户:截至11月13日17时6分,您的账户使用情况如下:
+本月消费:15.60元
+可用余额:21.84元
+仅供参考,实际费用以出账账单为准。更多详情点击:http://a.189.cn/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https://qy.k189.cn/hfcx?source=dt
+
+[2025-11-13 17:06:26.974][000000009.458] I/user.当前网络 true 1
+
+[2025-11-13 17:06:26.987][000000009.458] I/user.转发到飞书
+
+[2025-11-13 17:06:26.995][000000009.460] I/user.timestamp 1763024786
+
+[2025-11-13 17:06:27.003][000000009.460] I/user.sign 5cVjGl0UHK4BZiBTdjBfjzTE8a35JEmTQK/rXf75oLg=
+
+[2025-11-13 17:06:27.015][000000009.460] I/user.url https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a
+
+[2025-11-13 17:06:27.025][000000009.462] I/user.feishu 
+
+[2025-11-13 17:06:27.040][000000009.462] {"content":{"text":"我的id是nil,Thu Nov 13 17:06:26 2025,Air780EPM,    10001发来短信,内容是:尊敬的用户:截至11月13日17时6分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"sign":"5cVjGl0UHK4BZiBTdjBfjzTE8a35JEmTQK\/rXf75oLg=","msg_type":"text","timestamp":"1763024786"}
+
+[2025-11-13 17:06:27.054][000000009.467] dns_run 676:open.feishu.cn state 0 id 2 ipv6 0 use dns server2, try 0
+
+[2025-11-13 17:06:27.067][000000009.488] dns_run 693:dns all done ,now stop
+
+[2025-11-13 17:06:27.978][000000010.568] I/user.飞书机器人 success 200 
+
+[2025-11-13 17:06:27.991][000000010.568] {"Request-Id":"8b2b41bb-44fb-407e-a8ec-d7227179de61","X-Lgw-Dst-Svc":"aiRj-YZHJsmNioW5sRb1upcRDZKIYZgCiYCAuFKmezjGgFkw3Oe1lIOCyvhP_Tbkq8sMeWcR51hfWhQREaNR7XZZN_6jaoXyeKbKzxpJrUso_-lOboFMGg6LjrNjJrjvrdYhVd5fvKkNKkUf-sAMG-BN_ukN8d0A5HuDZNRFu4krRWwvY3Ee1UEjwIIS9IiHAGYb7HVYNS_NasM_sSXDswFx41MqTr9m9Yl_tY5bwOh1P0-dZRPL2GI61HqmhbaL0wR3NG8_Ppr3ZtvmLstaKA_CtycSJNqyd967TLYAUGPnCt6yyCMhH5skrUex-z6yzeQBM-jA8QLlaoPYrminYmhniN8NbRk0DiUjymWP9z8vGgs=","Content-Length":"77","Via":"vcache10.cn1402[393,0]","X-Timestamp":"1763024787.823","x-tt-trace-id":"00-251113170627B62D987D40E5B056503D-528A6D0B755B43FB-00","Date":"Thu, 13 Nov 2025 09:06:27 GMT","x-tt-trace-host":"01aa075810167c68795fcfe90f58fc27e8e757cc9914653a0080ebb138fb9e2e3f9b6bc2a8693e676821e360d1544d5c7a26bf0e1b103a5e197317b89ab6e941264ec78bcf2248fb662438b17b75f606f9cf951d77da3c828cae140a86c9d631d9","X-Tt-Logid":"20251113170627B62D987D40E5B056503D","Server":"Tengine","server-timing":"cdn-cache;desc=MISS,edge;dur=0,origin;dur=393","Tt_stable":"1","x-tt-trace-tag":"id=03;cdn-cache=miss;type=dyn","Content-Type":"application\/json","X-Request-Id":"8b2b41bb-44fb-407e-a8ec-d7227179de61","Timing-Allow-Origin":"*","Connection":"keep-alive","EagleId":"65597d1e17630247874392399e"}
+
+[2025-11-13 17:06:28.006][000000010.568]  {"StatusCode":0,"StatusMessage":"success","code":0,"data":{},"msg":"success"}
+
+[2025-11-13 17:06:28.973][000000011.569] I/user.转发到钉钉
+
+[2025-11-13 17:06:28.979][000000011.570] I/user.timestamp 1763024788000
+
+[2025-11-13 17:06:28.983][000000011.571] I/user.sign HOgxLj23Pl%2B3%2FMJ%2BAAyU%2FJXz7AwAMk%2BvqK0CrcxJqXA%3D
+
+[2025-11-13 17:06:28.987][000000011.571] I/user.url https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b&timestamp=1763024788000&sign=HOgxLj23Pl%2B3%2FMJ%2BAAyU%2FJXz7AwAMk%2BvqK0CrcxJqXA%3D
+
+[2025-11-13 17:06:29.005][000000011.572] I/user.dingding {"text":{"content":"我的id是nil,Thu Nov 13 17:06:28 2025,Air780EPM,    10001发来短信,内容是:尊敬的用户:截至11月13日17时6分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+
+[2025-11-13 17:06:29.023][000000011.574] dns_run 676:oapi.dingtalk.com state 0 id 3 ipv6 0 use dns server2, try 0
+
+[2025-11-13 17:06:29.034][000000011.631] dns_run 693:dns all done ,now stop
+
+[2025-11-13 17:06:29.767][000000012.360] I/user.钉钉机器人 success 200 {"Cache-Control":"no-cache","Date":"Thu, 13 Nov 2025 09:06:29 GMT","Connection":"keep-alive","Server":"DingTalk\/1.0.0","Content-Length":"27","Content-Type":"application\/json"} {"errcode":0,"errmsg":"ok"}
+
+[2025-11-13 17:06:30.766][000000013.361] I/user.转发到微信
+
+[2025-11-13 17:06:30.788][000000013.361] I/user.timestamp 1763024790000
+
+[2025-11-13 17:06:30.809][000000013.362] I/user.url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d&timestamp=1763024790000
+
+[2025-11-13 17:06:30.823][000000013.363] I/user.weixin {"text":{"content":"我的id是nil,Thu Nov 13 17:06:30 2025,Air780EPM,    10001发来短信,内容是:尊敬的用户:截至11月13日17时6分,您的账户使用情况如下:\n本月消费:15.60元\n可用余额:21.84元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+
+[2025-11-13 17:06:30.841][000000013.365] dns_run 676:qyapi.weixin.qq.com state 0 id 4 ipv6 0 use dns server2, try 0
+
+[2025-11-13 17:06:30.853][000000013.408] dns_run 693:dns all done ,now stop
+
+[2025-11-13 17:06:31.647][000000014.239] I/user.企业微信机器人 success 200 {"Content-Type":"application\/json; charset=UTF-8","Server":"nginx","Error-Code":"0","Date":"Thu, 13 Nov 2025 09:06:31 GMT","Connection":"keep-alive","Content-Length":"27","Error-Msg":"ok","X-W-No":"4"} {"errcode":0,"errmsg":"ok"}
+
+
+
+```

+ 204 - 0
module/Air780EPM/demo/sms/sms_app.lua

@@ -0,0 +1,204 @@
+--[[
+@module  sms_forward
+@summary 短信信息转发驱动模块
+@version 1.0
+@date    2025.10.15
+@author  王城钧
+@usage
+本文件为来电信息转发驱动模块,核心业务逻辑为:
+1、配置飞书,钉钉,企业微信机器人的webhook和secret(加签)。
+2、send_sms(),发送短信的功能函数,等待IP_READY消息后,手机卡可以进行收发短信。
+3、receive_sms(),接收短信处理的功能函数,收到短信后获取来信号码和短信内容,通过回调函数sms_handler(num, txt)转发到指定的机器人。
+
+本文件没有对外接口,直接在main.lua中require "sms_forward"就可以加载运行;
+]]
+
+-- webhook_feishu和secret_feishu要换成你自己机器人的值
+-- webhook_feishu是钉钉分配给机器人的URL
+-- secret_feishu是选取 "加签", 自动生成的密钥
+-- 下面的给一个测试群发消息, 随时可能关掉, 请换成你自己的值
+local webhook_feishu = "https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a"
+local secret_feishu = "qlf8UXrJc7RYtJLx77jRVh"
+
+local webhook_dingding =
+"https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b"
+local secret_dingding = "SEC1bec4c6416b14c945806fa658840c7fbc64c3257aacd0c72f6cad5d22e3d29a4"
+
+--local webhook_weixin = "https://work.weixin.qq.com/wework_admin/common/openBotProfile/24caa08b3a985454055047454d883fc98f"
+local webhook_weixin = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d"
+-- 飞书关于机器人的文档 https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
+
+--1.功能函数:短信转发到飞书
+local function feishu_post_sms(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    -- LuatOS的时间戳只到秒,飞书也只需要秒
+    local timestamp = tostring(os.time())
+    local sign = crypto.hmac_sha256("", timestamp .. "\n" .. secret_feishu):fromHex():toBase64()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    -- 注意, 这里的参数跟钉钉不同, 钉钉有个access_token参数, 飞书没有
+    local url = webhook_feishu
+    log.info("url", url)
+    -- json格式也需要按飞书的来
+    local data = { msg_type = "text" }
+    data["timestamp"] = timestamp
+    data["sign"] = sign
+    -- text就是要发送的文本内容, 其他格式按飞书的要求拼接table就好了
+    local text = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["content"] = { text = text }
+    local rbody = (json.encode(data))
+    log.info("feishu", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, rbody, { timeout = 5000 }).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("飞书机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--2.功能函数:短信转发到钉钉
+local function dingding_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+    -- LuatOS的时间戳只到秒,但钉钉需要毫秒,补3个零
+    local timestamp = tostring(os.time()) .. "000"
+    local sign = crypto.hmac_sha256(timestamp .. "\n" .. secret_dingding, secret_dingding):fromHex():toBase64()
+        :urlEncode()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    local url = webhook_dingding .. "&timestamp=" .. timestamp .. "&sign=" .. sign
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("dingding", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("钉钉机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--3.功能函数:短信转发到企业微信
+local function weixin_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    local timestamp = tostring(os.time()) .. "000"
+    log.info("timestamp", timestamp)
+    local url = webhook_weixin .. "&timestamp=" .. timestamp
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("weixin", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("企业微信机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--4.功能函数:接收短信的回调函数
+local function sms_handler(num, txt)
+    -- num 给我发短信的手机号码
+    -- txt 收到的短信文本内容
+
+    log.info("转发到飞书")
+    feishu_post_sms(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到钉钉")
+    dingding_post(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到微信")
+    weixin_post(num, txt)
+end
+
+--------------------------------------------------------------------
+--5. 功能函数:接收短信
+local function receive_sms()
+    while 1 do
+        local ret, num, txt = sys.waitUntil("SMS_INC", 30000)
+        log.info("收到来自短信:", num)
+        if num then
+            log.info("num是", num)
+            log.info("收到来自" .. num .. "的短信:" .. txt)
+
+            --local isReady1, index1 = socket.adapter()
+            log.info("当前网络", socket.adapter())
+
+            sms_handler(num, txt)
+        end
+    end
+end
+
+-------------------------------------------------------------------
+-- 6.功能函数:发送短信, 直接调用sms.send就行, 是不是task无所谓
+local function send_sms()
+--按照规范的做法,这里应该等待"SMS_READY"消息,
+    --目前内核固件正在开发支持"SMS_READY"消息功能,
+    --等开发好了之后,再使用"SMS_READY"消息,
+    --当前阶段,先使用"IP_READY"替代
+    sys.waitUntil("IP_READY")
+    sys.wait(5000) 
+    -- 当前阶段等待5s,使得sms准备就绪,待"SMS_READY"消息开发完成后,删除如上两行代码使用sys.waitUntil("SMS_READY")替代
+    log.info("发送短信准备就绪")
+    local cont = 1
+    log.info("开始发短信")
+    while 1 do
+        log.info("现在可以收发短信")
+
+        --获取本机号码,如果卡商没写入会返回nil
+        log.info("mobile.number(id) = ", mobile.number())
+        --获取本机iccid,如果卡商没写入会返回nil
+        log.info("mobile.iccid(id) = ", mobile.iccid())
+        --获取本机simid,如果卡商没写入会返回nil
+        log.info("mobile.simid(id) = ", mobile.simid())
+        --获取本机imsi,如果卡商没写入会返回nil
+        log.info("mobile.imsi(index) = ", mobile.imsi())
+        -- 注意:以下查话费的三行代码只需根据自己卡的运营商打开其一即可,其余两行关闭,不要全部打开
+        -- 电信卡查话费
+        local result = sms.send("10001", "102")
+        -- 中移动卡查短信
+        -- local result = sms.send("10086", "301")
+        -- 联通卡查话费
+        -- local result = sms.send("10010", "101")
+        -- 注意:V2018及更高版本固件才有"SMS_SENT"系统消息
+        if result then
+            local wait_msg, success  = sys.waitUntil("SMS_SENT", 10000)
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:", wait_msg and (success and "成功" or "失败") or "超时")
+        else
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:同步发送失败")
+        end
+        log.info("等待10分钟")
+        cont = cont + 1
+        sys.wait(10 * 60 * 1000)
+    end
+end
+
+
+--发送短信
+sys.taskInit(send_sms)
+--接收短信
+sys.taskInit(receive_sms)

+ 66 - 0
module/Air780EPM/demo/sms/sntp_app.lua

@@ -0,0 +1,66 @@
+--[[
+@module  sntp_app
+@summary sntp时间同步应用功能模块 
+@version 1.0
+@date    2025.10.29
+@author  王城钧
+@usage
+本文件为sntp时间同步应用功能模块,核心业务逻辑为:
+1、连接ntp服务器进行时间同步;
+2、如果同步成功,1小时之后重新发起同步动作;
+3、如果同步失败,10秒钟之后重新发起同步动作;
+
+本文件没有对外接口,直接在其他应用功能模块中require "sntp_app"就可以加载运行;
+]]
+
+-- sntp时间同步的任务处理函数
+local function sntp_task_func() 
+
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("sntp_task_func", "wait IP_READY", socket.dft())
+            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+            -- 或者等待1秒超时退出阻塞等待状态;
+            -- 注意:此处的1000毫秒超时不要修改的更长;
+            -- 因为当使用libnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+            -- 当libnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+            sys.waitUntil("IP_READY", 1000)
+        end
+
+        -- 检测到了IP_READY消息
+        log.warn("sntp_task_func", "recv IP_READY")
+
+        -- 发起ntp时间同步动作
+        socket.sntp()
+
+        -- 等待ntp时间同步结果,30秒超时失败,通常只需要几百毫秒就能成功
+        local ret = sys.waitUntil("NTP_UPDATE", 30000)
+
+        --同步成功
+        if ret then
+            -- 以下是获取/打印时间的演示,注意时区问题
+            log.info("sntp_task_func", "时间同步成功", "本地时间", os.date())
+            log.info("sntp_task_func", "时间同步成功", "UTC时间", os.date("!%c"))
+            log.info("sntp_task_func", "时间同步成功", "RTC时钟(UTC时间)", json.encode(rtc.get()))
+            log.info("sntp_task_func", "时间同步成功", "本地时间戳", os.time())
+            local t = os.date("*t")
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date() json格式", json.encode(t))
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date(os.time())", os.time(t))
+
+            -- 正常使用, 一小时一次, 已经足够了, 甚至1天一次也可以
+            sys.wait(3600000) 
+        --同步失败
+        else
+            log.info("sntp_task_func", "时间同步失败")
+            -- 10秒后重新发起同步动作
+            sys.wait(10000) 
+        end
+    end
+end
+
+--创建并且启动一个task
+--运行这个task的主函数sntp_task_func
+sys.taskInit(sntp_task_func)
+

+ 65 - 73
module/Air8000/demo/sms/main.lua

@@ -1,87 +1,79 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "smsdemo"
-VERSION = "1.0.0"
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.10.15
+@author  王城钧
+@usage
+1. sms_app:加载短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块
+2. netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+    (1) netdrv_4g:4G网卡
+    (2) netdrv_wifi:WIFI STA网卡
+    (3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+    (4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+    (5) netdrv_pc: pc模拟器网卡
+3. sntp_app:启动sntp时间同步功能模块,同步网络时间
+]]
 
+
+--[[
+必须定义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 = "sms_app"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
-require "sysplus" -- http库需要这个sysplus
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
-log.info("main", "sms demo")
 
--- 辅助发送http请求, 因为http库需要在task里运行
-function http_post(url, headers, body)
-    sys.taskInit(function()
-        local code, headers, body = http.request("POST", url, headers, body).wait()
-        log.info("resp", code)
-    end)
-end
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-function sms_handler(num, txt)
-    -- num 手机号码
-    -- txt 文本内容
-    log.info("sms", num, txt, txt:toHex())
 
-    -- http演示1, 发json
-    local body = json.encode({phone=num, txt=txt})
-    local headers = {}
-    headers["Content-Type"] = "application/json"
-    log.info("json", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示2, 发表单的
-    headers = {}
-    headers["Content-Type"] = "application/x-www-form-urlencoded"
-    local body = string.format("phone=%s&txt=%s", num:urlEncode(), txt:urlEncode())
-    log.info("params", body)
-    http_post("http://www.luatos.com/api/sms/blackhole", headers, body)
-    -- http演示3, 不需要headers,直接发
-    http_post("http://www.luatos.com/api/sms/blackhole", nil, num .. "," .. txt)
-    -- 如需发送到钉钉, 参考 demo/dingding
-    -- 如需发送到飞书, 参考 demo/feishu
-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)
+
 
---------------------------------------------------------------------
--- 接收短信, 支持多种方式, 选一种就可以了
--- 1. 设置回调函数
---sms.setNewSmsCb(sms_handler)
--- 2. 订阅系统消息
---sys.subscribe("SMS_INC", sms_handler)
--- 3. 在task里等着
-sys.taskInit(function()
-    while 1 do
-        local ret, num, txt = sys.waitUntil("SMS_INC", 300000)
-        if num then
-            -- 方案1, 交给自定义函数处理
-            sms_handler(num, txt)
-            -- 方案2, 因为这里是task内, 可以直接调用http.request
-            -- local body = json.encode({phone=num, txt=txt})
-            -- local headers = {}
-            -- headers["Content-Type"] = "application/json"
-            -- log.info("json", body)
-            -- local code, headers, body = http.request("POST", "http://www.luatos.com/api/sms/blackhole", headers, body).wait()
-            -- log.info("resp", code)
-        end
-    end
-end)
+-- 加载sms应用功能模块
+require "sms_app"
 
--------------------------------------------------------------------
--- 发送短信, 直接调用sms.send就行, 是不是task无所谓
-sys.taskInit(function()
-    sys.wait(10000)
-    -- 中移动卡查短信
-    -- sms.send("+8610086", "301")
-    -- 联通卡查话费
-     sms.send("10010", "101")
-end)
+-- 加载网络驱动设备功能模块
+require "netdrv_device"
 
+-- 加载sntp时间同步应用功能模块
+require "sntp_app"
 
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
+-- 启动系统调度(必须放在最后)
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 33 - 0
module/Air8000/demo/sms/netdrv/netdrv_4g.lua

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

+ 84 - 0
module/Air8000/demo/sms/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,84 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是Air8000开发板
+-- GPIO140为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(140, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    local result = spi.setup(
+        1,--spi_id
+        nil,
+        0,--CPHA
+        0,--CPOL
+        8,--数据宽度
+        25600000--,--频率
+        -- spi.MSB,--高低位顺序    可选,默认高位在前
+        -- spi.master,--主模式     可选,默认主
+        -- spi.full--全双工       可选,默认全双工
+    )
+    log.info("netdrv_eth_spi", "spi open result", result)
+    --返回值为0,表示打开成功
+    if result ~= 0 then
+        log.error("netdrv_eth_spi", "spi open error",result)
+        return
+    end
+
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 1, 片选 GPIO12
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=1, cs=12})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 94 - 0
module/Air8000/demo/sms/netdrv/netdrv_multiple.lua

@@ -0,0 +1,94 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air8000开发板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 140,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=1, cs=12}
+                }
+            },
+
+            -- WIFI STA网卡
+            {
+                WIFI = {
+                    -- 要连接的WIFI路由器名称
+                    ssid = "iPhone",
+                    -- 要连接的WIFI路由器密码
+                    password = "xiaoshuai",
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 45 - 0
module/Air8000/demo/sms/netdrv/netdrv_pc.lua

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

+ 49 - 0
module/Air8000/demo/sms/netdrv/netdrv_wifi.lua

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

+ 37 - 0
module/Air8000/demo/sms/netdrv_device.lua

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

+ 167 - 0
module/Air8000/demo/sms/readme.md

@@ -0,0 +1,167 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
+
+3、sms_app.lua:短信发送+短信接收+短信转发到企业微信/钉钉/飞书平台功能模块; 
+
+4、sntp_app.lua:sntp时间同步功能模块
+
+## 演示功能概述
+
+使用Air8000开发板测试sms功能
+
+1、短信发送功能;
+
+2、短信接收功能;
+
+3、netdrv_device:短信通过http转发到企业微信/钉钉/飞书平台时,配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+
+(1) netdrv_4g:4G网卡
+
+(2) netdrv_wifi:WIFI STA网卡
+
+(3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+
+(4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+
+4、NTP时间同步
+
+## 演示硬件环境
+
+[](https://docs.openluat.com/air8000/luatos/app/image/netdrv_multi.jpg)
+
+1、Air8000开发板一块+手机sim卡一张+4g天线一根+wifi天线一根+网线一根:
+
+* sim卡插入开发板的sim卡槽
+
+* 天线装到开发板上
+
+* 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air8000开发板和数据线的硬件接线方式为:
+
+* Air8000开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[必须使用Air8000 V2018或者更高版本](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/core)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2018-1固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+* 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+* 如果需要单WIFI STA网卡,打开require "netdrv_wifi",其余注释掉;同时netdrv_wifi.lua中的wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+* 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+* 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+3、修改sms_forward.lua文件中的webhook_feishu和webhook_dingding以及webhook_weixi,如需加密也可以填写对应app的secret,打开对应运营商的查话费代码
+
+3、烧录内核固件和sms相关demo成功后,自动开机运行。
+
+4、可以看到代码运行结果如下,不管是在选择什么网卡场景下,基本都是如下情况:
+
+以下是默认使用4G网卡下使用sms demo演示的日志
+
+日志中如果出现以下类似打印则说明短信转发成功:
+
+```
+[2025-10-15 20:14:01.867][000000008.505] I/user.现在可以收发短信
+
+[2025-10-15 20:14:01.887][000000008.505] I/user.mobile.number(id) =  +8617374070417
+
+[2025-10-15 20:14:01.910][000000008.506] I/user.mobile.iccid(id) =  89861123045773964016
+
+[2025-10-15 20:14:01.927][000000008.506] I/user.mobile.simid(id) =  0
+
+[2025-10-15 20:14:01.944][000000008.506] I/user.mobile.imsi(index) =  460115188098492
+
+[2025-10-15 20:14:01.961][000000008.507] D/sms pdu len 18
+
+[2025-10-15 20:14:01.979][000000008.508] I/user.发送查询短信 这是第1次发送  发送结果: 成功
+
+[2025-10-15 20:14:02.017][000000008.817] luat_sms_proc 1239:[DIO 1239]: CMI_SMS_SEND_MSG_CNF is in
+
+[2025-10-15 20:14:02.034][000000008.817] I/sms long sms callback seqNum = 1
+
+[2025-10-15 20:14:03.802][000000010.607] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-10-15 20:14:03.835][000000010.607] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-10-15 20:14:03.859][000000010.608] I/sms long-sms, wait more frags 3/3
+
+[2025-10-15 20:14:03.943][000000010.751] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-10-15 20:14:03.994][000000010.752] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-10-15 20:14:04.012][000000010.752] I/sms long-sms, wait more frags 1/3
+
+[2025-10-15 20:14:04.145][000000010.951] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+
+[2025-10-15 20:14:04.165][000000010.951] D/sms dcs 2 | 0 | 0 | 0
+
+[2025-10-15 20:14:04.182][000000010.952] I/sms long-sms is ok
+
+[2025-10-15 20:14:04.198][000000010.954] I/user.收到来自短信: 10001
+
+[2025-10-15 20:14:04.217][000000010.954] I/user.num是 10001
+
+[2025-10-15 20:14:04.238][000000010.955] I/user.收到来自10001的短信:尊敬的用户:截至10月15日20时14分,您的账户使用情况如下:
+本月消费:17.61元
+可用余额:28.83元
+仅供参考,实际费用以出账账单为准。更多详情点击:http://a.189.cn/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https://qy.k189.cn/hfcx?source=dt
+
+[2025-10-15 20:14:04.262][000000010.955] I/user.当前网络 true 1
+
+[2025-10-15 20:14:04.284][000000010.956] I/user.当前wifi网络情况 true 1
+
+[2025-10-15 20:14:04.306][000000010.956] I/user.转发到飞书
+
+[2025-10-15 20:14:04.323][000000010.957] I/user.timestamp 1760530443
+
+[2025-10-15 20:14:04.340][000000010.958] I/user.sign 4bHYm0YfpVjsdPIEugQBGfjuuyCYnI+Jjaf8K1zYqwo=
+
+[2025-10-15 20:14:04.358][000000010.958] I/user.url https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a
+
+[2025-10-15 20:14:04.384][000000010.959] I/user.feishu 
+
+[2025-10-15 20:14:04.403][000000010.959] {"content":{"text":"我的id是nil,Wed Oct 15 20:14:03 2025,Air8000,    10001发来短信,内容是:尊敬的用户:截至10月15日20时14分,您的账户使用情况如下:\n本月消费:17.61元\n可用余额:28.83元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?
+source=dt"},"sign":"4bHYm0YfpVjsdPIEugQBGfjuuyCYnI+Jjaf8K1zYqwo=","msg_type":"text","timestamp":"1760530443"}
+[2025-10-15 20:14:04.430][000000010.964] dns_run 676:open.feishu.cn state 0 id 2 ipv6 0 use dns server2, try 0
+
+[2025-10-15 20:14:04.447][000000010.995] dns_run 693:dns all done ,now stop
+
+[2025-10-15 20:14:06.166][000000012.974] I/user.转发到钉钉
+
+[2025-10-15 20:14:06.225][000000012.975] I/user.timestamp 1760530445000
+
+[2025-10-15 20:14:06.265][000000012.975] I/user.sign N2JtlrhtDtRoAlfEJj2cBQPIXQGO8KMURW2UGday1lA%3D
+
+[2025-10-15 20:14:06.294][000000012.976] I/user.url https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b&timestamp=1760530445000&sign=N2JtlrhtDtRoAlfEJj2cBQPIXQGO8KMURW2UGday1lA%3D
+
+[2025-10-15 20:14:06.357][000000012.977] I/user.dingding {"text":{"content":"我的id是nil,Wed Oct 15 20:14:05 2025,Air8000,    10001发来短信,内容是:尊敬的用户:截至10月15日20时14分,您的账户使用情况如下:\n本月消费:17.61元\n可用余额:28.83元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+[2025-10-15 20:14:06.401][000000012.979] dns_run 676:oapi.dingtalk.com state 0 id 3 ipv6 0 use dns server2, try 0
+
+[2025-10-15 20:14:06.424][000000013.014] dns_run 693:dns all done ,now stop
+
+[2025-10-15 20:14:08.906][000000015.708] I/user.转发到微信
+
+[2025-10-15 20:14:08.935][000000015.708] I/user.timestamp 1760530448000
+
+[2025-10-15 20:14:08.953][000000015.709] I/user.url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d&timestamp=1760530448000
+
+[2025-10-15 20:14:08.971][000000015.710] I/user.weixin {"text":{"content":"我的id是nil,Wed Oct 15 20:14:08 2025,Air8000,    10001发来短信,内容是:尊敬的用户:截至10月15日20时14分,您的账户使用情况如下:\n本月消费:17.61元\n可用余额:28.83元\n仅供参考,实际费用以出账账单为准。更多详情点击:http:\/\/a.189.cn\/IRAYnu 进入“中国电信”APP。也可关注“浙江电信”微信公众号进行快速查询:https:\/\/qy.k189.cn\/hfcx?source=dt"},"msgtype":"text"}
+
+
+```

+ 203 - 0
module/Air8000/demo/sms/sms_app.lua

@@ -0,0 +1,203 @@
+--[[
+@module  sms_forward
+@summary 短信信息转发驱动模块
+@version 1.0
+@date    2025.10.15
+@author  王城钧
+@usage
+本文件为来电信息转发驱动模块,核心业务逻辑为:
+1、配置飞书,钉钉,企业微信机器人的webhook和secret(加签)。
+2、send_sms(),发送短信的功能函数,等待CC_IND消息后,手机卡可以进行收发短信。
+3、receive_sms(),接收短信处理的功能函数,收到短信后获取来信号码和短信内容,通过回调函数sms_handler(num, txt)转发到指定的机器人。
+
+本文件没有对外接口,直接在main.lua中require "sms_forward"就可以加载运行;
+]]
+
+-- webhook_feishu和secret_feishu要换成你自己机器人的值
+-- webhook_feishu是钉钉分配给机器人的URL
+-- secret_feishu是选取 "加签", 自动生成的密钥
+-- 下面的给一个测试群发消息, 随时可能关掉, 请换成你自己的值
+local webhook_feishu = "https://open.feishu.cn/open-apis/bot/v2/hook/673d1e1d-0c7e-4d34-b7f0-48bdb4c4d03a"
+local secret_feishu = "qlf8UXrJc7RYtJLx77jRVh"
+
+local webhook_dingding =
+"https://oapi.dingtalk.com/robot/send?access_token=bf9fe5c74194b9556cff401b87ac5de46a92bbf15cc226b73d14c28773b86f3b"
+local secret_dingding = "SEC1bec4c6416b14c945806fa658840c7fbc64c3257aacd0c72f6cad5d22e3d29a4"
+
+--local webhook_weixin = "https://work.weixin.qq.com/wework_admin/common/openBotProfile/24caa08b3a985454055047454d883fc98f"
+local webhook_weixin = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a9dec355-3e0f-45bf-a0b1-0f8813fe6b7d"
+-- 飞书关于机器人的文档 https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
+
+--1.功能函数:短信转发到飞书
+local function feishu_post_sms(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    -- LuatOS的时间戳只到秒,飞书也只需要秒
+    local timestamp = tostring(os.time())
+    local sign = crypto.hmac_sha256("", timestamp .. "\n" .. secret_feishu):fromHex():toBase64()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    -- 注意, 这里的参数跟钉钉不同, 钉钉有个access_token参数, 飞书没有
+    local url = webhook_feishu
+    log.info("url", url)
+    -- json格式也需要按飞书的来
+    local data = { msg_type = "text" }
+    data["timestamp"] = timestamp
+    data["sign"] = sign
+    -- text就是要发送的文本内容, 其他格式按飞书的要求拼接table就好了
+    local text = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["content"] = { text = text }
+    local rbody = (json.encode(data))
+    log.info("feishu", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, rbody, { timeout = 5000 }).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("飞书机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--2.功能函数:短信转发到钉钉
+local function dingding_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+    -- LuatOS的时间戳只到秒,但钉钉需要毫秒,补3个零
+    local timestamp = tostring(os.time()) .. "000"
+    local sign = crypto.hmac_sha256(timestamp .. "\n" .. secret_dingding, secret_dingding):fromHex():toBase64()
+        :urlEncode()
+    log.info("timestamp", timestamp)
+    log.info("sign", sign)
+    local url = webhook_dingding .. "&timestamp=" .. timestamp .. "&sign=" .. sign
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("dingding", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("钉钉机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--3.功能函数:短信转发到企业微信
+local function weixin_post(num, rctxt)
+    local rheaders = {}
+    rheaders["Content-Type"] = "application/json"
+
+    local timestamp = tostring(os.time()) .. "000"
+    log.info("timestamp", timestamp)
+    local url = webhook_weixin .. "&timestamp=" .. timestamp
+    log.info("url", url)
+    local data = { msgtype = "text" }
+    -- content就是要发送的文本内容, 其他格式按钉钉的要求拼接table就好了
+    local content = "我的id是" ..
+        tostring(device_id) .. "," .. (os.date()) .. "," .. rtos.bsp() .. ",    " .. num .. "发来短信,内容是:" .. rctxt
+    data["text"] = { content = content }
+    local rbody = (json.encode(data))
+    log.info("weixin", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, (json.encode(data))).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("企业微信机器人", 
+    code==200 and "success" or "error", 
+    code, 
+    json.encode(headers or {}), 
+    body and (body:len()>512 and body:len() or body) or "nil")
+end
+
+--4.功能函数:接收短信的回调函数
+local function sms_handler(num, txt)
+    -- num 给我发短信的手机号码
+    -- txt 收到的短信文本内容
+
+    log.info("转发到飞书")
+    feishu_post_sms(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到钉钉")
+    dingding_post(num, txt)
+
+    --等待1秒, 非必须
+    sys.wait(1000)
+    log.info("转发到微信")
+    weixin_post(num, txt)
+end
+
+--------------------------------------------------------------------
+--5. 功能函数:接收短信
+local function receive_sms()
+    while 1 do
+        local ret, num, txt = sys.waitUntil("SMS_INC", 30000)
+        log.info("收到来自短信:", num)
+        if num then
+            log.info("num是", num)
+            log.info("收到来自" .. num .. "的短信:" .. txt)
+
+            --local isReady1, index1 = socket.adapter()
+            log.info("当前网络", socket.adapter())
+
+            sms_handler(num, txt)
+        end
+    end
+end
+
+-------------------------------------------------------------------
+-- 6.功能函数:发送短信, 直接调用sms.send就行, 是不是task无所谓
+local function send_sms()
+    --系统消息CC_IND到了才能收发短信
+    --按照规范的做法,这里应该等待"SMS_READY"消息,
+    --目前内核固件正在开发支持"SMS_READY"消息功能,
+    --等开发好了之后,再使用"SMS_READY"消息,
+    --当前阶段,先使用"CC_IND"替代
+    sys.waitUntil("CC_IND")
+    log.info("发送短信前wait CC_IND")
+    local cont = 1
+    log.info("开始发短信")
+    while 1 do
+        log.info("现在可以收发短信")
+
+        --获取本机号码,如果卡商没写入会返回nil
+        log.info("mobile.number(id) = ", mobile.number())
+        --获取本机iccid,如果卡商没写入会返回nil
+        log.info("mobile.iccid(id) = ", mobile.iccid())
+        --获取本机simid,如果卡商没写入会返回nil
+        log.info("mobile.simid(id) = ", mobile.simid())
+        --获取本机imsi,如果卡商没写入会返回nil
+        log.info("mobile.imsi(index) = ", mobile.imsi())
+        -- 注意:以下查话费的三行代码只需根据自己卡的运营商打开其一即可,其余两行关闭,不要全部打开
+        -- 电信卡查话费
+        local result = sms.send("10001", "102")
+        -- 中移动卡查短信
+        -- local result = sms.send("10086", "301")
+        -- 联通卡查话费
+        -- local result = sms.send("10010", "101")
+        -- 注意:V2018及更高版本固件才有"SMS_SENT"系统消息
+        if result then
+            local wait_msg, success  = sys.waitUntil("SMS_SENT", 10000)
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:", wait_msg and (success and "成功" or "失败") or "超时")
+        else
+            log.info("发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:同步发送失败")
+        end
+        log.info("等待10分钟")
+        cont = cont + 1
+        sys.wait(10 * 60 * 1000)
+    end
+end
+
+
+--发送短信
+sys.taskInit(send_sms)
+--接收短信
+sys.taskInit(receive_sms)

+ 66 - 0
module/Air8000/demo/sms/sntp_app.lua

@@ -0,0 +1,66 @@
+--[[
+@module  sntp_app
+@summary sntp时间同步应用功能模块 
+@version 1.0
+@date    2025.10.29
+@author  王城钧
+@usage
+本文件为sntp时间同步应用功能模块,核心业务逻辑为:
+1、连接ntp服务器进行时间同步;
+2、如果同步成功,1小时之后重新发起同步动作;
+3、如果同步失败,10秒钟之后重新发起同步动作;
+
+本文件没有对外接口,直接在其他应用功能模块中require "sntp_app"就可以加载运行;
+]]
+
+-- sntp时间同步的任务处理函数
+local function sntp_task_func() 
+
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("sntp_task_func", "wait IP_READY", socket.dft())
+            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+            -- 或者等待1秒超时退出阻塞等待状态;
+            -- 注意:此处的1000毫秒超时不要修改的更长;
+            -- 因为当使用libnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+            -- 当libnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+            sys.waitUntil("IP_READY", 1000)
+        end
+
+        -- 检测到了IP_READY消息
+        log.warn("sntp_task_func", "recv IP_READY")
+
+        -- 发起ntp时间同步动作
+        socket.sntp()
+
+        -- 等待ntp时间同步结果,30秒超时失败,通常只需要几百毫秒就能成功
+        local ret = sys.waitUntil("NTP_UPDATE", 30000)
+
+        --同步成功
+        if ret then
+            -- 以下是获取/打印时间的演示,注意时区问题
+            log.info("sntp_task_func", "时间同步成功", "本地时间", os.date())
+            log.info("sntp_task_func", "时间同步成功", "UTC时间", os.date("!%c"))
+            log.info("sntp_task_func", "时间同步成功", "RTC时钟(UTC时间)", json.encode(rtc.get()))
+            log.info("sntp_task_func", "时间同步成功", "本地时间戳", os.time())
+            local t = os.date("*t")
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date() json格式", json.encode(t))
+            log.info("sntp_task_func", "时间同步成功", "本地时间os.date(os.time())", os.time(t))
+
+            -- 正常使用, 一小时一次, 已经足够了, 甚至1天一次也可以
+            sys.wait(3600000) 
+        --同步失败
+        else
+            log.info("sntp_task_func", "时间同步失败")
+            -- 10秒后重新发起同步动作
+            sys.wait(10000) 
+        end
+    end
+end
+
+--创建并且启动一个task
+--运行这个task的主函数sntp_task_func
+sys.taskInit(sntp_task_func)
+