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

add:新增780EHV-sms/cc转发-demo

马亚丹 4 месяцев назад
Родитель
Сommit
fd4aecf612

+ 220 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/cc_forward.lua

@@ -0,0 +1,220 @@
+--[[
+@module  cc_forward
+@summary 来电信息转发驱动模块
+@version 1.0
+@date    2025.9.15
+@author  马亚丹
+@usage
+本文件为来电信息转发驱动模块,核心业务逻辑为:
+1、配置飞书,钉钉,企业微信机器人的webhook和secret(加签)。
+2、cc_setup(),初始化电话功能,做好接收来电的准备。
+3、cc_state(state),电话状态判断并获取来电号码,来电或者挂断等不同情况做不同处理。
+4、cc_forward(),来电号码信息转发到指定机器人
+
+直接使用Air780EHV核心板硬件测试即可;
+
+本文件没有对外接口,直接在main.lua中require "cc_forward"就可以加载运行;
+]]
+--------------------------------------------------------------------------------------
+-- webhook_feishu和secret_feishu要换成你自己机器人的值
+-- webhook_feishu是钉钉分配给机器人的URL
+-- secret_feishu是选取 "加签", 自动生成的密钥
+-- 下面的给一个测试群发消息, 随时可能关掉, 请换成你自己的值
+local webhook_feishu = "https://open.feishu.cn/open-apis/bot/v2/hook/bb089165-4b73-4f80-9ed0-da0c908b44e5"
+local secret_feishu = "dp9w8i5IZrrZQpLW0bTcI"
+
+local webhook_dingding =
+"https://oapi.dingtalk.com/robot/send?access_token=03f4753ec6aa6f0524fb85907c94b17f3fa0fed3107d4e8f4eee1d4a97855f4d"
+local secret_dingding = "SECac5b455d6b567f64073a456e91feec6ad26c0f8f7dcca85dd2ce6c23ea466c52"
+
+
+local webhook_weixin = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=71017f82-e027-4c5d-a618-eb4ee01750e9"
+-- 飞书关于机器人的文档 https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
+
+
+
+-- status,通话状态,string类型,取值如下:
+
+-- "READY":通话准备完成,可以拨打电话或者呼入电话了
+
+-- "INCOMINGCALL":有电话呼入
+
+-- "CONNECTED":电话已经接通
+
+-- "DISCONNECTED":电话被对方挂断
+
+-- "SPEECH_START":通话开始
+
+-- "MAKE_CALL_OK":拨打电话请求成功
+
+-- "MAKE_CALL_FAILED":拨打电话请求失败
+
+-- "ANSWER_CALL_DONE":接听电话请求完成
+
+-- "HANGUP_CALL_DONE":挂断电话请求完成
+
+-- "PLAY":开始有音频输出
+
+local cnt = 0
+local phone_num = nil
+local multimedia_id = 0 -- 音频通道 0
+
+
+
+
+--1.功能函数:来电转发到飞书
+local function feishu_post_cc(num)
+    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 .. "来电"
+    data["content"] = { text = text }
+    local rbody = (json.encode(data))
+    log.info("feishu", rbody)
+    local code, headers, body = http.request("POST", url, rheaders, rbody).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("feishu", code, body)
+end
+
+--2.功能函数:来电转发到钉钉
+local function dingding_post_cc(num)
+    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 .. "来电"
+    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("dingding", code, body)
+end
+
+--3.功能函数:来电转发到企业微信
+local function weixin_post_cc(num)
+    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 .. "来电"
+    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("weixin", code, body)
+end
+
+
+
+
+--4.初始化cc
+local function cc_setup()
+    --查看网卡适配器的联网状态是否IP_READY,true表示已经准备好可以联网了,false暂时不可以联网
+    while not socket.adapter(socket.dft()) do
+        log.warn("cc", "wait IP_READY", socket.dft())
+        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+        -- 或者等待1秒超时退出阻塞等待状态;
+        -- 注意:此处的1000毫秒超时不要修改的更长;
+        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+        sys.waitUntil("IP_READY", 1000)
+    end
+
+    -- 检测到了IP_READY消息,设置默认网络适配器编号
+    log.info("cc", "recv IP_READY", socket.dft())
+
+    --初始化电话功能
+    local cc_int = cc.init(multimedia_id)
+    if cc_int then
+        return true
+    else
+        lngo.info("初始化电话功能失败")
+    end
+end
+
+--5.转发号码
+local function cc_forward()
+    log.info(" 来电号码转发到飞书")
+    feishu_post_cc(phone_num)
+
+
+    log.info("来电号码转发到钉钉")
+    dingding_post_cc(phone_num)
+
+
+    log.info("来电号码转发到微信")
+    weixin_post_cc(phone_num)
+end
+
+--6.来电判断
+local function cc_state(state)
+    if state == "READY" then
+        log.info("通话准备完成,可以拨打电话或者呼入电话了")
+        --有电话呼入
+    elseif state == "INCOMINGCALL" then
+        if cnt == 0 then
+            log.info("获取最后一次通话的号码")
+            phone_num = cc.lastNum()
+            log.info("来电号码是:", phone_num)
+            if phone_num then
+                --转发通知来电
+                sys.taskInit(cc_forward)
+            end
+        end
+        cnt = cnt + 1
+        if cnt > 3 then
+            --自动接听
+            --cc.accept(0)
+
+            --响4声以后自动自动挂断
+            cc.hangUp()
+            cnt = 0
+        end
+        --电话被对方挂断
+    elseif state == "DISCONNECTED" then
+        cnt = 0
+    end
+end
+
+--7.功能测试主函数
+local function test_fun()
+    if not cc_setup() then
+        return
+    end
+    sys.subscribe("CC_IND", cc_state)
+end
+sys.taskInit(test_fun)

+ 85 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/main.lua

@@ -0,0 +1,85 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.9.10
+@author  马亚丹
+@usage
+1. 详细逻辑请看cc_forward文件和sms_forward文件
+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模拟器上的网卡
+
+]]
+
+
+--[[
+必须定义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 = "780EHV/sms_call_forward"
+VERSION = "001.000.000"
+
+require "sys"
+-- 在日志中打印项目名和项目版本号
+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)
+
+
+--加载cc_forward功能模块
+require "cc_forward"
+
+--加载sms_forward功能模块
+require "sms_forward"
+
+-- 加载网络驱动设备功能模块,在该文件中修改自己使用的联网方式
+require"netdrv_device"
+
+-- 加载sntp时间同步应用功能模块(转发短信是携带时间参数,socket需要时间同步功能)
+require "sntp_app"
+
+
+
+-- 启动系统调度(必须放在最后)
+sys.run()

+ 44 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/netdrv/netdrv_4g.lua

@@ -0,0 +1,44 @@
+--[[
+@module  netdrv_4g
+@summary “4G网卡”驱动模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为4G网卡驱动模块,核心业务逻辑为:
+1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)    
+    if adapter == socket.LWIP_GP then
+        -- 在位置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_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_GP))
+    end
+end
+
+local function ip_lose_func(adapter)    
+    if adapter == socket.LWIP_GP then
+        log.warn("netdrv_4g.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+-- 在Air8000上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
+

+ 70 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,70 @@
+--[[
+@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 exnetif = require "exnetif"
+
+local function ip_ready_func(ip, adapter)    
+    if adapter == socket.LWIP_ETH 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_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)    
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+-- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+-- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
+-- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
+-- 本demo使用Air8000开发板测试,开发板上的硬件配置为:
+-- GPIO140为CH390H以太网芯片的供电使能控制引脚
+-- 使用spi1,片选引脚使用GPIO12
+-- 如果使用的硬件不是Air8000开发板,根据自己的硬件配置修改以下参数
+exnetif.set_priority_order({
+    {
+        ETHERNET = {
+            pwrpin = 140, 
+            tp = netdrv.CH390,
+            opts = {spi = 1, cs = 12}
+        }
+    }
+})

+ 105 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/netdrv/netdrv_multiple.lua

@@ -0,0 +1,105 @@
+--[[
+@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)
+    -- 在位置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")
+    
+    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路由器名称
+                     -- 要连接的WIFI路由器名称
+                    ssid = "Mayadan",
+                    -- 要连接的WIFI路由器密码
+                    password = "12345678", 
+
+                    -- 连通性检测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/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/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)

+ 68 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/netdrv/netdrv_wifi.lua

@@ -0,0 +1,68 @@
+--[[
+@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 exnetif = require "exnetif"
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_STA 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_wifi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_STA))
+    end
+end
+
+local function ip_lose_func(adapter)    
+    if adapter == socket.LWIP_STA then
+        log.warn("netdrv_wifi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+--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网络是否连接成功
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察WIFI的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 配置WiFi设备模式的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_STA
+-- ssid为要连接的WiFi路由器名称;
+-- password为要连接的WiFi路由器密码;
+-- 注意:仅支持2.4G的WiFi,不支持5G的WiFi;
+-- 实际测试时,根据自己要连接的WiFi热点信息修改以下参数
+exnetif.set_priority_order({
+    {
+        WIFI = {
+            ssid = "茶室-降功耗,找合宙!", 
+            password = "Air123456"
+        }
+    }
+})
+

+ 37 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/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_ethernet_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"

+ 263 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/readme.md

@@ -0,0 +1,263 @@
+
+
+## 功能模块介绍:
+
+1、main.lua:主程序入口文件,加载以下 4个文件运行。
+
+2、netdrv_multiple.lua:网卡驱动配置文件,可以配置以太网卡,wifi 网卡,单 4g 网卡三种网卡的使用优先级
+
+3、sms_forward.lua: 短信转发功能模块文件
+
+4、cc_forward.lua:来电转发功能模块文件
+
+5、sntp_app.lua:时间同步应用功能模块
+
+6、netdrv_pc:pc模拟器上的网卡
+
+## 演示功能概述:
+
+**sms_forward.lua:**
+
+1、配置飞书,钉钉,企业微信机器人的 webhook 和 secret(加签)。
+
+2、send_sms(),发送短信的功能函数,等待 CC_IND 消息后,手机卡可以进行收发短信。
+
+3、receive_sms(),接收短信处理的功能函数,收到短信后获取来信号码和短信内容,通过回调函数 sms_handler(num, txt)转发到指定的机器人。
+
+**cc_forward.lua:**
+
+1、配置飞书,钉钉,企业微信机器人的 webhook 和 secret(加签)。
+
+2、cc_setup(),初始化电话功能,做好接收来电的准备。
+
+3、cc_state(state),电话状态判断并获取来电号码,来电或者挂断等不同情况做不同处理。
+
+4、cc_forward(),来电号码信息转发到指定机器人
+
+## 演示硬件环境:
+
+![8000w](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV.png)
+
+1、Air780EHV核心板一块+支持短信和电话功能的手机sim卡一张+网线一根:
+
+* sim卡插入开发板的sim卡槽
+
+* 天线装到开发板上
+
+* 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
+
+2、TYPE-C USB数据线一根 ,Air780EHV核心板和数据线的硬件接线方式为:
+
+Air780EHV核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+TYPE-C USB数据线直接插到开发板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+3、可选 AirETH_1000 配件板一块,Air780EHV 核心板和 AirETH_1000 配件板的硬件接线方式为:
+
+| **Air780EHV核心板** | **AirETH_1000配件板** |
+| ---------------- |:------------------:|
+| 3V3              | 3.3v               |
+| GND              | GND                |
+| 86/SPI0CLK       | SCK                |
+| 83/SPI0CS        | CSS                |
+| 84/SPI0MISO      | SDO                |
+| 85/SPI0MOSI      | SDI                |
+| 107/GPIO21       | INT                |
+
+4、Air780EHV核心板购买链接:[商品详情](https://item.taobao.com/item.htm?id=943253206359&spm=a1z10.3-c-s.w4002-24045920836.11.52ca6ee5XT5zBv)
+
+## 演示软件环境:
+
+1、 Luatools 下载调试工具
+
+2、 固件版本:LuatOS-SoC_V2014_Air780EHV_1.soc;参考[项目使用的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/);如有更新可以使用最新固件。](https://docs.openluat.com/air8000/luatos/firmware/)
+
+3、 脚本文件:
+
+main.lua
+
+netdrv_device.lua:
+
+sms_forward.lua:
+
+cc_forward.lua:
+
+netdrv文件夹
+
+4、 pc 系统 win11(win10 及以上)
+
+5、飞书,钉钉,企业微信等自己需要的机器人。
+
+## 演示核心步骤:
+
+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",其余注释掉
+
+* 如果需要pc模拟器网卡,打开require "netdrv_pc",其余注释掉
+
+* 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+3、[https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN](https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN) 参考此教程,获取飞书,钉钉,企业微信的 webhook 和 secret(加签),在 cc_forward.lua 和 sms_forward.lua 脚本中找到 local webhook_feishu,secret_feishu,webhook_dingding,secret_dingding,webhook_weixin 的参数定义,修改为自己的参数。
+
+4、Luatools 烧录内核固件和修改后的 demo 脚本代码
+
+5、netdrv_device.lua中默认使用require "netdrv_4g",即单4G网卡。
+
+烧录成功后,代码会自动运行,log 日志打印以太网信息, wif 网络信息、CC_READY 等消息,log 日志打印如下:
+
+```yaml
+[2025-11-11 11:38:47.437][000000000.365] self_info 127:model Air780EHV_A10 imei 867920075014846
+[2025-11-11 11:38:47.443][000000000.365] self_info 129:firmware[1] TTS+VOLTE
+[2025-11-11 11:38:47.452][000000000.365] self_info 131:zone(kbytes) fs 768 script 512
+[2025-11-11 11:38:47.456][000000000.366] I/main LuatOS@Air780EHV base 25.03 bsp V2016 32bit
+[2025-11-11 11:38:47.461][000000000.366] I/main ROM Build: Oct 10 2025 10:52:53
+[2025-11-11 11:38:47.465][000000000.369] W/pins /luadb/pins_AIR780EHV.json not exist!!
+[2025-11-11 11:38:47.475][000000000.372] D/main loadlibs luavm 4194296 16096 16096
+[2025-11-11 11:38:47.485][000000000.372] D/main loadlibs sys   3211688 104756 105984
+[2025-11-11 11:38:47.491][000000000.372] D/main loadlibs psram 3211688 104848 106076
+[2025-11-11 11:38:47.496][000000000.390] I/user.main cc_sms_forward 001.000.000
+[2025-11-11 11:38:47.500][000000000.397] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:47.505][000000000.423] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:47.785][000000001.398] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:47.816][000000001.424] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:48.785][000000002.399] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:48.815][000000002.425] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:49.789][000000003.400] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:49.818][000000003.425] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:50.781][000000004.401] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:50.810][000000004.425] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:51.790][000000005.402] W/user.cc wait IP_READY 1 1
+[2025-11-11 11:38:51.821][000000005.426] W/user.sntp_task_func wait IP_READY 1 1
+[2025-11-11 11:38:52.725][000000006.328] D/mobile cid1, state0
+[2025-11-11 11:38:52.732][000000006.329] D/mobile bearer act 0, result 0
+[2025-11-11 11:38:52.742][000000006.329] D/mobile TIME_SYNC 0
+[2025-11-11 11:38:52.750][000000006.329] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-11-11 11:38:52.760][000000006.330] I/user.netdrv_4g.ip_ready_func IP_READY 10.183.48.180 255.255.255.255 0.0.0.0 nil
+[2025-11-11 11:38:52.766][000000006.331] W/user.sntp_task_func recv IP_READY
+[2025-11-11 11:38:52.773][000000006.332] D/sntp query ntp.aliyun.com
+[2025-11-11 11:38:52.779][000000006.332] dns_run 676:ntp.aliyun.com state 0 id 1 ipv6 0 use dns server0, try 0
+[2025-11-11 11:38:52.787][000000006.334] I/user.cc recv IP_READY 1 1
+[2025-11-11 11:38:52.795][000000006.388] dns_run 693:dns all done ,now stop
+[2025-11-11 11:38:53.031][000000006.649] D/sntp Unix timestamp: 1762832332
+[2025-11-11 11:38:53.040][000000006.650] soc_cms_proc 2219:cenc report 1,51,1,15
+[2025-11-11 11:38:53.049][000000006.654] I/user.sntp_task_func 时间同步成功 本地时间 Tue Nov 11 11:38:52 2025
+[2025-11-11 11:38:53.057][000000006.654] I/user.sntp_task_func 时间同步成功 UTC时间 Tue Nov 11 03:38:52 2025
+[2025-11-11 11:38:53.073][000000006.655] I/user.sntp_task_func 时间同步成功 RTC时钟(UTC时间) {"year":2025,"min":38,"hour":3,"mon":11,"sec":52,"day":11}
+[2025-11-11 11:38:53.092][000000006.656] I/user.sntp_task_func 时间同步成功 本地时间戳 1762832332
+[2025-11-11 11:38:53.102][000000006.657] I/user.sntp_task_func 时间同步成功 本地时间os.date() json格式 {"wday":3,"min":38,"yday":315,"hour":11,"isdst":false,"year":2025,"month":11,"sec":52,"day":11}
+[2025-11-11 11:38:53.112][000000006.657] I/user.sntp_task_func 时间同步成功 本地时间os.date(os.time()) 1762861132
+[2025-11-11 11:38:53.118][000000006.737] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-11-11 11:38:53.132][000000006.738] I/user.netdrv_4g.ip_ready_func IP_READY 10.183.48.180 255.255.255.255 0.0.0.0 nil
+[2025-11-11 11:38:54.659][000000008.275] D/mobile ims reg state 0
+[2025-11-11 11:38:54.670][000000008.276] D/mobile LUAT_MOBILE_EVENT_CC status 0
+[2025-11-11 11:38:54.681][000000008.276] D/mobile LUAT_MOBILE_CC_READY
+[2025-11-11 11:38:54.694][000000008.277] I/user.发送短信前wait CC_IND
+[2025-11-11 11:38:54.699][000000008.278] I/user.现在可以收发短信
+[2025-11-11 11:38:54.704][000000008.278] I/user.mobile.number(id) =  nil
+[2025-11-11 11:38:54.714][000000008.278] I/user.mobile.iccid(id) =  89860325743780541565
+[2025-11-11 11:38:54.719][000000008.279] I/user.mobile.simid(id) =  0
+[2025-11-11 11:38:54.726][000000008.279] I/user.mobile.imsi(index) =  460115726670673
+[2025-11-11 11:38:54.733][000000008.279] D/sms pdu len 18
+[2025-11-11 11:38:54.744][000000008.281] I/user.通话准备完成,可以拨打电话或者呼入电话了
+
+
+```
+
+此处短信演示使用了电信卡,发送"102"给"10001"查询余额,会收到电信回复的短信,并转发到飞书、钉钉和微信,log 打印如下:
+
+```bash
+[2025-11-11 11:38:54.699][000000008.278] I/user.现在可以收发短信
+[2025-11-11 11:38:54.704][000000008.278] I/user.mobile.number(id) =  nil
+[2025-11-11 11:38:54.714][000000008.278] I/user.mobile.iccid(id) =  89860325743780541565
+[2025-11-11 11:38:54.719][000000008.279] I/user.mobile.simid(id) =  0
+[2025-11-11 11:38:54.726][000000008.279] I/user.mobile.imsi(index) =  460115726670673
+[2025-11-11 11:38:54.733][000000008.279] D/sms pdu len 18
+[2025-11-11 11:38:54.744][000000008.281] I/user.通话准备完成,可以拨打电话或者呼入电话了
+[2025-11-11 11:38:55.279][000000008.900] luat_sms_proc 1239:[DIO 1239]: CMI_SMS_SEND_MSG_CNF is in
+[2025-11-11 11:38:55.289][000000008.900] I/sms long sms callback seqNum = 1
+[2025-11-11 11:38:56.251][000000009.867] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+[2025-11-11 11:38:56.264][000000009.868] D/sms dcs 2 | 0 | 0 | 0
+[2025-11-11 11:38:56.272][000000009.869] I/sms long-sms, wait more frags 2/2
+[2025-11-11 11:38:59.153][000000012.766] luat_sms_proc 1236:[DIO 1236]: CMI_SMS_NEW_MSG_IND is in
+[2025-11-11 11:38:59.163][000000012.767] D/sms dcs 2 | 0 | 0 | 0
+[2025-11-11 11:38:59.175][000000012.767] I/sms long-sms is ok
+[2025-11-11 11:38:59.182][000000012.769] I/user.num是 10001
+[2025-11-11 11:38:59.195][000000012.770] I/user.收到来自10001的短信:截止到2025年11月11日11时,本机可用余额:45.4元,帐户余额:45.4,欠费:0.0元 查询结果仅供参考,实际费用以出账单为准。更多服务请微信关注【河南电信】公众号,或下载欢go客户端( http://a.189.cn/JJTh4u )。
+[2025-11-11 11:38:59.226][000000012.770] I/user.转发到飞书
+[2025-11-11 11:38:59.235][000000012.772] I/user.timestamp 1762832338
+[2025-11-11 11:38:59.245][000000012.772] I/user.sign hS1xPjJCUwNaWP5j/CZuZ+eVJOJ52rs7MZF1iOWSfcg=
+[2025-11-11 11:38:59.257][000000012.773] I/user.url https://open.feishu.cn/open-apis/bot/v2/hook/bb089165-4b73-4f80-9ed0-da0c908b44e5
+[2025-11-11 11:38:59.267][000000012.775] I/user.feishu {"content":{"text":"我的id是nil,Tue Nov 11 11:38:58 2025,Air780EHV,    10001发来短信,内容是:截止到2025年11月11日11时,本机可用余额:45.4元,帐户余额:45.4,欠费:0.0元 查询结果仅供参考,实际费用以出账单为准。更多服务请微信关注【河南电信】公众号,或下载欢go客户端( http:\/\/a.189.cn\/JJTh4u )。"},"sign":"hS1xPjJCUwNaWP5j\/CZuZ+eVJOJ52rs7MZF1iOWSfcg=","msg_type":"text","timestamp":"1762832338"}
+[2025-11-11 11:38:59.274][000000012.781] dns_run 676:open.feishu.cn state 0 id 2 ipv6 0 use dns server0, try 0
+[2025-11-11 11:38:59.285][000000012.825] dns_run 693:dns all done ,now stop
+[2025-11-11 11:38:59.700][000000013.317] I/user.给10001发送查询短信 这是第1次发送  发送结果: true
+[2025-11-11 11:39:00.405][000000014.017] I/user.feishu 200 {"StatusCode":0,"StatusMessage":"success","code":0,"data":{},"msg":"success"}
+[2025-11-11 11:39:00.415][000000014.017] I/user.转发到钉钉
+[2025-11-11 11:39:00.428][000000014.019] I/user.timestamp 1762832339000
+[2025-11-11 11:39:00.439][000000014.019] I/user.sign kgaeJUMqVQfYzxpqu%2B80dTRY24V0PKZnhrV%2FnjcL69c%3D
+[2025-11-11 11:39:00.445][000000014.019] I/user.url https://oapi.dingtalk.com/robot/send?access_token=03f4753ec6aa6f0524fb85907c94b17f3fa0fed3107d4e8f4eee1d4a97855f4d&timestamp=1762832339000&sign=kgaeJUMqVQfYzxpqu%2B80dTRY24V0PKZnhrV%2FnjcL69c%3D
+[2025-11-11 11:39:00.462][000000014.021] I/user.dingding {"text":{"content":"我的id是nil,Tue Nov 11 11:38:59 2025,Air780EHV,    10001发来短信,内容是:截止到2025年11月11日11时,本机可用余额:45.4元,帐户余额:45.4,欠费:0.0元 查询结果仅供参考,实际费用以出账单为准。更多服务请微信关注【河南电信】公众号,或下载欢go客户端( http:\/\/a.189.cn\/JJTh4u )。"},"msgtype":"text"}
+[2025-11-11 11:39:00.472][000000014.022] dns_run 676:oapi.dingtalk.com state 0 id 3 ipv6 0 use dns server0, try 0
+[2025-11-11 11:39:00.486][000000014.066] dns_run 693:dns all done ,now stop
+[2025-11-11 11:39:01.117][000000014.738] I/user.dingding 200 {"errcode":0,"errmsg":"ok"}
+[2025-11-11 11:39:01.123][000000014.739] I/user.转发到微信
+[2025-11-11 11:39:01.128][000000014.739] I/user.timestamp 1762832340000
+[2025-11-11 11:39:01.145][000000014.739] I/user.url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=71017f82-e027-4c5d-a618-eb4ee01750e9&timestamp=1762832340000
+[2025-11-11 11:39:01.156][000000014.741] I/user.weixin {"text":{"content":"我的id是nil,Tue Nov 11 11:39:00 2025,Air780EHV,    10001发来短信,内容是:截止到2025年11月11日11时,本机可用余额:45.4元,帐户余额:45.4,欠费:0.0元 查询结果仅供参考,实际费用以出账单为准。更多服务请微信关注【河南电信】公众号,或下载欢go客户端( http:\/\/a.189.cn\/JJTh4u )。"},"msgtype":"text"}
+[2025-11-11 11:39:01.174][000000014.742] dns_run 676:qyapi.weixin.qq.com state 0 id 4 ipv6 0 use dns server0, try 0
+[2025-11-11 11:39:01.184][000000014.790] dns_run 693:dns all done ,now stop
+[2025-11-11 11:39:02.307][000000015.930] I/user.weixin 200 {"errcode":0,"errmsg":"ok"}
+
+
+```
+
+此处来电转发演示使用了电信卡,用另外手机拨打模组上的电话号码,响铃 4声后自动挂断,log 打印如下:
+
+```lua
+[2025-11-11 11:39:31.138][000000044.751] D/mobile LUAT_MOBILE_EVENT_CC status 12
+[2025-11-11 11:39:31.147][000000044.752] D/mobile LUAT_MOBILE_EVENT_CC status 1
+[2025-11-11 11:39:31.151][000000044.752] I/user.获取最后一次通话的号码
+[2025-11-11 11:39:31.160][000000044.754] I/user.来电号码是: 18317857567
+[2025-11-11 11:39:31.163][000000044.754] I/user. 来电号码转发到飞书
+[2025-11-11 11:39:31.166][000000044.755] I/user.timestamp 1762832370
+[2025-11-11 11:39:31.175][000000044.756] I/user.sign Be6GXYvLRzO6iIBvvbkd1MzzecU0Vg+hxwF1uyRmFBc=
+[2025-11-11 11:39:31.178][000000044.756] I/user.url https://open.feishu.cn/open-apis/bot/v2/hook/bb089165-4b73-4f80-9ed0-da0c908b44e5
+[2025-11-11 11:39:31.182][000000044.757] I/user.feishu {"content":{"text":"我的id是nil,Tue Nov 11 11:39:30 2025,Air780EHV,    18317857567来电"},"sign":"Be6GXYvLRzO6iIBvvbkd1MzzecU0Vg+hxwF1uyRmFBc=","msg_type":"text","timestamp":"1762832370"}
+[2025-11-11 11:39:31.190][000000044.759] dns_run 676:open.feishu.cn state 0 id 5 ipv6 0 use dns server0, try 0
+[2025-11-11 11:39:31.194][000000044.761] D/mobile LUAT_MOBILE_EVENT_CC status 2
+[2025-11-11 11:39:31.231][000000044.847] dns_run 693:dns all done ,now stop
+[2025-11-11 11:39:32.101][000000045.724] I/user.feishu 200 {"StatusCode":0,"StatusMessage":"success","code":0,"data":{},"msg":"success"}
+[2025-11-11 11:39:32.109][000000045.724] I/user.来电号码转发到钉钉
+[2025-11-11 11:39:32.115][000000045.726] I/user.timestamp 1762832371000
+[2025-11-11 11:39:32.129][000000045.726] I/user.sign 4%2B1ksJhCL5xqQ8zk%2BKrV7qTXq1EDtYqMISzyT5T2Ykg%3D
+[2025-11-11 11:39:32.133][000000045.726] I/user.url https://oapi.dingtalk.com/robot/send?access_token=03f4753ec6aa6f0524fb85907c94b17f3fa0fed3107d4e8f4eee1d4a97855f4d&timestamp=1762832371000&sign=4%2B1ksJhCL5xqQ8zk%2BKrV7qTXq1EDtYqMISzyT5T2Ykg%3D
+[2025-11-11 11:39:32.136][000000045.728] I/user.dingding {"text":{"content":"我的id是nil,Tue Nov 11 11:39:31 2025,Air780EHV,    18317857567来电"},"msgtype":"text"}
+[2025-11-11 11:39:32.140][000000045.729] dns_run 676:oapi.dingtalk.com state 0 id 6 ipv6 0 use dns server0, try 0
+[2025-11-11 11:39:32.178][000000045.776] dns_run 693:dns all done ,now stop
+[2025-11-11 11:39:32.782][000000046.390] I/user.dingding 200 {"errcode":0,"errmsg":"ok"}
+[2025-11-11 11:39:32.790][000000046.391] I/user.来电号码转发到微信
+[2025-11-11 11:39:32.793][000000046.391] I/user.timestamp 1762832371000
+[2025-11-11 11:39:32.795][000000046.392] I/user.url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=71017f82-e027-4c5d-a618-eb4ee01750e9&timestamp=1762832371000
+[2025-11-11 11:39:32.802][000000046.393] I/user.weixin {"text":{"content":"我的id是nil,Tue Nov 11 11:39:31 2025,Air780EHV,    18317857567来电"},"msgtype":"text"}
+[2025-11-11 11:39:32.806][000000046.394] dns_run 676:qyapi.weixin.qq.com state 0 id 7 ipv6 0 use dns server0, try 0
+[2025-11-11 11:39:32.811][000000046.428] dns_run 693:dns all done ,now stop
+[2025-11-11 11:39:33.618][000000047.230] I/user.weixin 200 {"errcode":0,"errmsg":"ok"}
+[2025-11-11 11:39:37.172][000000050.750] D/mobile LUAT_MOBILE_EVENT_CC status 1
+[2025-11-11 11:39:43.140][000000056.750] D/mobile LUAT_MOBILE_EVENT_CC status 1
+[2025-11-11 11:39:49.137][000000062.750] D/mobile LUAT_MOBILE_EVENT_CC status 1
+[2025-11-11 11:39:49.179][000000062.757] luat_i2s_load_old_config 287:i2s0 old param not saved!
+[2025-11-11 11:39:49.204][000000062.757] D/cc VOLTE_EVENT_PLAY_STOP
+[2025-11-11 11:39:49.223][000000062.758] D/mobile LUAT_MOBILE_EVENT_CC status 12
+[2025-11-11 11:39:49.293][000000062.911] soc_cms_proc 2219:cenc report 1,38,1,7
+[2025-11-11 11:39:49.322][000000062.912] D/mobile cid7, state2
+[2025-11-11 11:39:51.135][000000064.754] D/mobile LUAT_MOBILE_EVENT_CC status 10
+
+
+```

+ 200 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/sms_forward.lua

@@ -0,0 +1,200 @@
+--[[
+@module  sms_forward
+@summary 短信信息转发驱动模块
+@version 1.0
+@date    2025.9.15
+@author  马亚丹
+@usage
+本文件为来电信息转发驱动模块,核心业务逻辑为:
+1、配置飞书,钉钉,企业微信机器人的webhook和secret(加签)。
+2、send_sms(),发送短信的功能函数,等待CC_IND消息后,手机卡可以进行收发短信。
+3、receive_sms(),接收短信处理的功能函数,收到短信后获取来信号码和短信内容,通过回调函数sms_handler(num, txt)转发到指定的机器人。
+
+直接使用Air780EHV核心板板硬件测试即可;
+
+本文件没有对外接口,直接在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/bb089165-4b73-4f80-9ed0-da0c908b44e5"
+local secret_feishu = "dp9w8i5IZrrZQpLW0bTcI"
+
+local webhook_dingding =
+"https://oapi.dingtalk.com/robot/send?access_token=03f4753ec6aa6f0524fb85907c94b17f3fa0fed3107d4e8f4eee1d4a97855f4d"
+local secret_dingding = "SECac5b455d6b567f64073a456e91feec6ad26c0f8f7dcca85dd2ce6c23ea466c52"
+
+
+local webhook_weixin = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=71017f82-e027-4c5d-a618-eb4ee01750e9"
+-- 飞书关于机器人的文档 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).wait()
+    -- 正常会返回 200, {"errcode":0,"errmsg":"ok"}
+    -- 其他错误, 一般是密钥错了, 仔细检查吧
+    log.info("feishu", code, body)
+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("dingding", code, body)
+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("weixin", code, body)
+end
+
+
+
+
+
+--4.功能函数:接收短信的回调函数
+local function sms_handler(num, txt)
+    -- num 给我发短信的手机号码
+    -- txt 收到的短信文本内容
+
+
+    log.info("转发到飞书")
+    feishu_post_sms(num, txt)
+
+
+    
+    log.info("转发到钉钉")
+    dingding_post(num, txt)
+
+    
+    log.info("转发到微信")
+    weixin_post(num, txt)
+end
+
+
+--------------------------------------------------------------------
+--5. 功能函数:接收短信, 支持多种方式, 选一种就可以了
+-----5.1. 设置回调函数
+--sms.setNewSmsCb(sms_handler)
+-----5.2. 订阅系统消息
+--sys.subscribe("SMS_INC", sms_handler)
+-- 5.3. 在task里等着
+local function receive_sms()
+    while 1 do
+        local ret, num, txt = sys.waitUntil("SMS_INC", 300000)
+        if num then
+            log.info("num是", num)
+            log.info("收到来自" .. num .. "的短信:" .. txt)
+
+            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
+    while 1 do
+        log.info("现在可以收发短信")
+
+        --获取本机号码,如果卡商没写入会返回nil
+        log.info("mobile.number(id) = ", mobile.number())
+        --获取本机iccid,失败返回nil
+        log.info("mobile.iccid(id) = ", mobile.iccid())
+        --获取本机simid,失败返回-1
+        log.info("mobile.simid(id) = ", mobile.simid())
+        --获取本机imsi,失败返回nil
+        log.info("mobile.imsi(index) = ", mobile.imsi())
+
+        --给自己发短信
+        --local result = sms.send("1593868****", "test")
+
+        --电信卡给10001发送“102”查话费
+        local result = sms.send("10001", "102")
+        if result then
+            --sms.send 的同步结果仅表示任务启动成功,
+            --最终发送状态需通过异步事件 "SMS_SEND_RESULT" 获取;
+            --目前内核固件正在开发支持"SMS_SEND_RESULT"消息功能
+            --当前阶段,sys.waitUntil等不到这个消息,最终5秒超时退出。
+            sys.waitUntil("SMS_SEND_RESULT",5*1000)
+            log.info("给10001发送查询短信", "这是第" .. cont .. "次发送", " 发送结果:", result)
+        else
+            log.warn("sms", "短信发送失败")
+        end
+        cont = cont + 1
+        sys.wait(10 * 60 * 1000)
+    end
+end
+
+
+--发送短信
+sys.taskInit(send_sms)
+--接收短信
+sys.taskInit(receive_sms)

+ 66 - 0
module/Air780EHM_Air780EHV_Air780EGH/project/sms_call_forward/sntp_app.lua

@@ -0,0 +1,66 @@
+--[[
+@module  sntp_app
+@summary sntp时间同步应用功能模块 
+@version 1.0
+@date    2025.07.01
+@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毫秒超时不要修改的更长;
+            -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+            -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+            sys.waitUntil("IP_READY", 1000)
+        end
+
+        -- 检测到了IP_READY消息
+        log.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)
+