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

add: 提交Air780系列和Air8101的lora代码

wangshihao 3 месяцев назад
Родитель
Сommit
0cfb733303

+ 170 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/lora2_main.lua

@@ -0,0 +1,170 @@
+--[[
+@module  lora2_main
+@summary lora2功能测试主模块
+@version 1.0
+@date    2025.11.24
+@author  王世豪
+@usage
+本功能模块演示的内容为:
+1. lora2设备初始化与配置
+2. SPI接口初始化
+3. 发送和接收参数配置
+4. 事件回调处理
+5. 发送和接收任务管理
+
+本文件没有对外接口,直接在main.lua中require "lora2_main"就可以加载运行;
+]]
+
+-- 加载依赖模块
+local lora2_sender = require "lora2_sender"
+local lora2_receiver = require "lora2_receiver"
+
+local TASK_NAME = "lora2_task"
+
+local spi_id = 0 -- SPI接口ID
+local pin_cs = 8 -- 片选引脚
+local pin_reset = 1 -- 复位控制引脚
+local pin_busy = 16 -- 忙状态指示引脚
+local pin_dio1 = 17 -- DIO1引脚
+
+
+local RECEIVE_TIMEOUT = 3000  -- 接收超时时间3秒
+
+--[[
+event值有:
+    tx_done     -- 发送完成:数据已成功发送
+    rx_done     -- 接收完成:成功接收到数据
+    rx_timeout  -- 接收超时:在指定时间内未收到数据
+    rx_error    -- 接收错误:接收过程中发生错误
+__]]
+function callback(lora_device, event, data, size)
+    if event == "tx_done" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "tx_done")
+    elseif event == "rx_done" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_done", data, size)
+    elseif event == "rx_timeout" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_timeout")
+    elseif event == "rx_error" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_error")
+    else
+        log.warn("未知事件类型:", event)
+    end
+end
+
+local function lora2_init()
+    -- 初始化SPI
+    spi_lora = spi_lora or spi.deviceSetup(spi_id,pin_cs,0,0,8,10*1000*1000,spi.MSB,1,0)
+    if not spi_lora then
+        log.error("spi_lora init failed")
+        return false
+    end
+
+    -- 初始化LORA2设备
+    -- 当前支持型号:llcc68, sx1262
+    lora_device = lora_device or lora2.init("llcc68",{res = pin_reset,busy = pin_busy,dio1 = pin_dio1},spi_lora)
+    if not lora_device then
+        log.error("lora_device init failed")
+        return false
+    end
+    log.info("lora_device",lora_device)
+
+    -- 设置频道频率为433MHz
+    lora_device:set_channel(433000000) 
+
+    -- 配置 lora 设备的发送参数
+    lora_device:set_txconfig({
+        mode=1, 
+        power=22,
+        fdev=0,
+        bandwidth=0,
+        datarate=9,
+        coderate=4,
+        preambleLen=8,
+        fixLen=false,
+        crcOn=true,
+        freqHopOn=0,
+        hopPeriod=0,
+        iqInverted=false
+    })
+
+    -- 配置 lora 设备的接收参数
+    lora_device:set_rxconfig({
+        mode=1,
+        bandwidth=0,
+        datarate=9,
+        coderate=4,
+        bandwidthAfc=0,
+        preambleLen=8,
+        symbTimeout=0,
+        fixLen=false,
+        payloadLen=0,
+        crcOn=true,
+        freqHopOn=0,
+        hopPeriod=0,
+        iqInverted=false,
+        rxContinuous=false
+    })
+
+    return true
+end
+
+local function lora2_main_task_func()
+    local result,msg
+
+    while true do
+        result = lora2_init()
+        if not result then
+            log.info("lora2_init error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 注册回调
+        lora_device:on(callback)
+
+        -- 默认初始化后启动接收
+        lora_device:recv(RECEIVE_TIMEOUT)
+
+        --- 发送设备就绪事件,将lora_device传递给sender模块
+        sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "DEVICE_READY", lora_device) 
+
+        while true do
+            msg = sys.waitMsg(TASK_NAME, "LORA_EVENT")
+
+            if msg[2]== "tx_done" then
+                log.info("lora2_main", "发送完成")
+                -- 通知sender模块发送完成
+                sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "TX_DONE")
+                -- 发送完成后启动接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+            elseif msg[2]== "rx_done" then
+                log.info("lora2_main", "接收完成", "数据长度:", msg[4])
+                -- 交由receiver模块处理数据
+                lora2_receiver.proc(msg[3], msg[4], lora_device)
+                -- 处理完成后启动接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+            elseif msg[2]== "rx_timeout" then
+                log.info("lora2_main", "接收超时")
+                -- 接收超时后继续接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+                -- 接收过程中发生错误
+            elseif msg[2]== "rx_error" then
+                log.info("lora2_main", "接收错误")
+                -- 接收错误后继续接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+            end
+        end
+        
+        -- 出现异常
+        ::EXCEPTION_PROC::
+
+        -- 清空此task绑定的消息队列中的未处理的消息
+        sys.cleanMsg(TASK_NAME)
+
+        sys.wait(5000)
+    end
+end
+
+sys.taskInitEx(lora2_main_task_func, TASK_NAME)

+ 25 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/lora2_receiver.lua

@@ -0,0 +1,25 @@
+--[[
+@module  lora2_receiver
+@summary lora数据接收应用功能模块
+@version 1.0
+@date    2025.11.24
+@author  王世豪
+@usage
+本文件为lora数据接收应用功能模块,核心业务逻辑为:
+1. 处理接收到的lora数据。
+
+本文件的对外接口有:
+1. lora2_receiver.proc(data): 处理接收到的lora数据。
+2. sys.publish("LORA_RECV_DATA", data): 发布收到的数据给其他模块处理。
+]]
+
+local lora2_receiver = {}
+
+-- 处理接收到的lora数据
+function lora2_receiver.proc(data, size)
+    log.info("lora2_receiver", "收到数据", size, data)
+    -- 发布数据给其他模块
+    sys.publish("LORA_RECV_DATA", data)
+end
+
+return lora2_receiver

+ 131 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/lora2_sender.lua

@@ -0,0 +1,131 @@
+--[[
+@module  lora2_sender
+@summary lora数据发送应用功能模块
+@version 1.0
+@date    2025.11.24
+@author  王世豪
+@usage
+本文件为lora数据发送应用功能模块,核心业务逻辑为:
+1、订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、lora2_sender task接收"DEVICE_READY"、"SEND_REQ"、"TX_DONE"消息,处理队列中的数据;
+    "DEVICE_READY"消息表示lora设备已初始化完成,"SEND_REQ"消息表示有新数据需要发送,"TX_DONE"消息表示数据发送完成;
+4、数据发送完成后通过回调函数通知发送方。
+
+本文件的对外接口有:
+1. sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func); 订阅"SEND_DATA_REQ"消息;
+    其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据、回调函数和回调参数一起publish出去;
+]]
+
+local lora2_sender = {}
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+}
+data: 要发送的数据,string类型,必须存在;
+cb.func: 数据发送结果的用户回调函数,可以不存在;
+cb.para: 数据发送结果的用户回调函数参数,可以不存在;
+]]
+
+local send_queue = {}
+
+lora2_sender.TASK_NAME = "lora2_sender"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 将数据插入到发送队列send_queue中
+    table.insert(send_queue, {data=data, cb=cb})
+    -- 发送消息通知 lora sender task,有新数据等待发送
+    sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "SEND_REQ") 
+    log.info("队列", #send_queue)
+end
+
+-- 按照顺序发送send_queue中的数据
+-- 发送请求提交后,返回当前正在发送的数据项,等待发送完成事件
+-- 如果设备未初始化,则通知回调函数发送失败,并继续处理下一条
+local function send_item_func(lora_device)
+    local item
+    -- 如果发送队列中有数据等待发送
+    while #send_queue>0 do
+        -- 取出来第一条数据赋值给item
+        -- 同时从队列send_queue中删除这一条数据
+        item = table.remove(send_queue, 1)
+
+        -- 检查设备是否初始化
+        if not lora_device then
+            log.error("lora2_sender", "设备未初始化")
+            -- 通知回调函数发送失败
+            if item.cb and item.cb.func then
+                item.cb.func(false, item.cb.para)
+            end
+            return nil
+        end
+        
+        -- 发送数据
+        lora_device:send(item.data)
+        
+        -- 返回当前发送项,等待发送完成"TX_DONE"事件
+        return item
+    end
+    return nil
+end
+
+-- 处理发送结果的回调函数
+local function send_item_cbfunc(item, result)
+    if item then
+        -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if item.cb and item.cb.func then
+            item.cb.func(result, item.cb.para)
+        end
+    end
+end
+
+-- lora client sender的任务处理函数
+local function lora2_sender_task_func()
+    local lora_device
+    local send_item = nil
+    local msg
+
+    while true do
+        -- 等待"LORA_EVENT"消息
+        msg = sys.waitMsg(lora2_sender.TASK_NAME, "LORA_EVENT")
+        log.info("lora2_sender", "收到消息", msg[2])
+        
+        -- 设备就绪事件
+        if msg[2] == "DEVICE_READY" then
+            lora_device = msg[3]
+            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
+            if lora_device and not send_item then
+                send_item = send_item_func(lora_device)
+            end
+
+        -- 发送数据请求
+        elseif msg[2] == "SEND_REQ" then
+            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
+            if lora_device and not send_item then
+                send_item = send_item_func(lora_device)
+            end
+        -- 发送完成事件
+        elseif msg[2] == "TX_DONE" then
+            -- 通知回调函数发送成功
+            send_item_cbfunc(send_item, true)
+            -- 清空当前发送项
+            send_item = nil
+            -- 继续处理队列中的下一条数据
+            send_item = send_item_func(lora_device)
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
+-- 本demo项目中uart_app.lua中publish了这个消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+--创建并且启动一个task
+--运行这个task的处理函数lora2_sender_task_func
+sysplus.taskInitEx(lora2_sender_task_func, lora2_sender.TASK_NAME)
+
+return lora2_sender

+ 65 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/main.lua

@@ -0,0 +1,65 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0.0
+@date    2025.11.24
+@author  王世豪
+@usage
+本demo演示的功能为:
+1. lora2设备初始化与配置(SPI接口初始化、lora芯片配置)
+2. lora2无线数据发送与接收功能
+3. 串口与lora数据互转功能
+4. 事件回调机制与任务管理
+5. 数据缓冲区管理与超时处理
+
+]]
+--[[
+必须定义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 = "LORA2_DEMO"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    wdt.init(9000) -- 初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+-- 加载ADC测量功能模块
+require "lora2_main"
+
+-- 加载串口功能模块
+require "uart_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!
+
+

+ 120 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/readme.md

@@ -0,0 +1,120 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、lora2_main.lua:lora主应用功能模块;
+
+3、lora2_sender.lua:lora数据发送应用功能模块;
+
+4、lora2_receiver.lua:lora数据接收应用功能模块;
+
+5、uart_app.lua:UART应用功能模块;
+
+
+## 演示功能概述
+
+使用Air780EHM/Air780EHV/Air780EGH核心板+lora模块测试lora的数据发送和接收功能。
+
+需要两套Air780EHM/Air780EHV/Air780EGH核心板+lora模块,才能进行有效的通信测试。
+
+## 演示硬件环境
+
+1、Air780EHM/Air780EHV/Air780EGH 核心板两块 + 同型号lora模块两块(芯片选择llcc68/sx1268);
+
+本演示中,使用的lora模块为Ai-Thinker的Ra-01SC模块,该模块基于llcc68芯片。
+
+![image](https://docs.openLuat.com/cdn/image/Ai-Thinker_lora模块.png)
+
+[淘宝购买链接 点击此处](https://detail.tmall.com/item.htm?ali_refid=a3_430673_1006%3A1310580056%3AN%3AbqpGoPHh6maprV%2FiOvfLQlttIGZ5%2F0H4%3A8ac800110c31e390e3a00be4332367ec&ali_trackid=1_8ac800110c31e390e3a00be4332367ec&id=642897064381&mi_id=00007gKI69Jf4rfCAQlHSi0FwOhhsjkApkWKIk0aR6x63s4&mm_sceneid=1_0_1079550197_0&priceTId=2147872c17636209574944284e0e8c&skuId=4696650265078&spm=a21n57.sem.item.2&utparam=%7B%22aplus_abtest%22%3A%2204f30be1eb4a5e73cebb07691a717fb3%22%7D&xxc=ad_ztc)
+
+
+2、TYPE-C USB数据线一根,Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为:
+
+- 核心板通过TYPE-C USB口供电,核心板正面的 ON/OFF 拨动开关 拨到ON一端;
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- USB转串口数据线,一般来说,白线连接核心板的UART1_TX,绿线连接核心板的UART1_RX,黑线连接核心板的GND,另外一端连接电脑USB口;
+
+3、核心板通过SPI接口与lora模块连接并进行通信,具体接线如下:
+
+![image](https://docs.openLuat.com/cdn/image/Air780EHV_lora.jpg)
+
+| Air780EXX核心板  |  lora模块          |
+| --------------- | ----------------- |
+| 3V3             | VCC               |
+| GND             | GND               |
+| 86/SPI0CLK      | SCK               |
+| 83/SPI0CS       | CSS               |
+| 84/SPI0MISO     | MISO              |
+| 85/SPI0MOSI     | MOSI              |
+| 22/GPIO1        | RST               |
+| 97/GPIO16       | BUSY              |
+| 100/GPIO17      | DIO1              |
+
+硬件连接注意事项:
+
+1. 电源要求:
+   - 不同lora模块的供电电压范围可能存在差异,请根据具体模块规格确定合适的电源电压
+
+2. 控制信号连接:
+   - RST引脚:连接至核心板的GPIO1,用于lora模块复位控制
+  
+   - BUSY引脚:连接至核心板的GPIO16,用于lora模块忙状态指示
+  
+   - DIO1引脚:连接至核心板的GPIO17,用于lora模块中断信号接收
+  
+
+3. 连接确认:
+   - 上述GPIO引脚分配已在测试中验证可用
+  
+   - 如有变更需求,需同步修改软件配置中的引脚定义
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、固件获取地址:
+
+[Air780EHM 固件](https://docs.openluat.com/air780epm/luatos/firmware/version/#air780ehmluatos)
+
+[Air780EHV 固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[Air780EGH 固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+
+3、PC端的串口工具,例如SSCOM、LLCOM等都可以;
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、打开PC端的串口工具,选择对应的端口,配置波特率115200,数据位8,停止位1,无奇偶校验位;
+
+5、如下是Air780EHM/Air780EHV/Air780EGH核心板搭配lora模块发送和接收数据的演示结果:(核心板+lora模块,下面统称为lora设备)
+
+(1)上电后,lora设备A和B会进入lora接收状态,等待接收数据; 若接收超时,会继续等待接收, 所以在没有数据传输时,luatools会一直打印接收超时信息。
+
+```lua
+I/user.lora2_main 接收超时
+```
+
+(2)设备A发送数据,设备B接收数据:
+  
+   - 发送端通过UART1发送字符串"Hello, I am LoRa device A!"
+
+   - 接收端通过UART1成功接收到数据"Hello, I am LoRa device A!"
+  
+(2)设备B发送数据,设备A接收数据:
+  
+   - 发送端通过UART1发送字符串"Hello, I am LoRa device B!"
+
+   - 接收端通过UART1成功接收到数据"Hello, I am LoRa device B!"
+
+![image](https://docs.openLuat.com/cdn/image/lora_uart_test.png)
+
+```

+ 75 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lora2/uart_app.lua

@@ -0,0 +1,75 @@
+--[[
+@module  uart_app
+@summary 串口应用功能模块
+@version 1.0
+@date    2025.11.24
+@author  王世豪
+@usage
+本文件为串口应用功能模块,核心业务逻辑为:
+1、打开uart1,波特率115200,数据位8,停止位1,无奇偶校验位;
+2、uart1和pc端的串口工具相连;
+3、从uart1接收到pc端串口工具发送的数据后,通过lora发送;
+4、收到lora数据后,将数据通过uart1发送到pc端串口工具;
+
+本文件的对外接口有两个:
+1、sys.publish("SEND_DATA_REQ", "uart", read_buf) ,通知lora2_sender 模块发送read_buf数据;
+2、sys.subscribe("LORA_RECV_DATA", recv_data_from_lora_proc),订阅LORA_RECV_DATA消息,处理消息携带的数据;
+]]
+
+
+-- 使用UART1
+local UART_ID = 1
+-- 串口接收数据缓冲区
+local read_buf = ""
+
+-- 末尾增加回车换行两个字符,通过uart发送出去,方便在PC端换行显示查看
+local function recv_data_from_lora_proc(data)
+    uart.write(UART_ID, data.."\r\n")
+end
+
+
+local function concat_timeout_func()
+    -- 如果存在尚未处理的串口缓冲区数据;
+    -- 将数据通过publish通知其他应用功能模块处理;
+    -- 然后清空本文件的串口缓冲区数据
+    if read_buf:len() > 0 then
+        sys.publish("SEND_DATA_REQ", "uart", read_buf)
+        read_buf = ""
+    end
+end
+
+
+-- UART1的数据接收中断处理函数,UART1接收到数据时,会执行此函数
+local function read()
+    local s
+    while true do
+        -- 非阻塞读取UART1接收到的数据,最长读取1024字节
+        s = uart.read(UART_ID, 1024)
+
+        -- 如果从串口没有读到数据
+        if not s or s:len() == 0 then
+            -- 启动50毫秒的定时器,如果50毫秒内没收到新的数据,则处理当前收到的所有数据
+            -- 这样处理是为了防止将一大包数据拆分成多个小包来处理
+            -- 例如pc端串口工具下发1100字节的数据,可能会产生将近20次的中断进入到read函数,才能读取完整
+            -- 此处的50毫秒可以根据自己项目的需求做适当修改,在满足整包拼接完整的前提下,时间越短,处理越及时
+            sys.timerStart(concat_timeout_func, 50)
+            -- 跳出循环,退出本函数
+            break
+        end
+
+        log.info("uart_app.read len", s:len())
+
+        -- 将本次从串口读到的数据拼接到串口缓冲区read_buf中
+        read_buf = read_buf..s
+    end
+end
+
+-- 初始化UART1,波特率115200,数据位8,停止位1
+uart.setup(UART_ID, 115200, 8, 1)
+
+-- 注册UART1的数据接收中断处理函数,UART1接收到数据时,会执行read函数
+uart.on(UART_ID, "receive", read)
+
+-- 订阅"LORA_RECV_DATA"消息的处理函数recv_data_from_lora_proc
+-- 收到"LORA_RECV_DATA"消息后,会执行函数recv_data_from_lora_proc
+sys.subscribe("LORA_RECV_DATA", recv_data_from_lora_proc)

+ 2 - 2
module/Air780EPM/demo/lora2/lora2_main.lua

@@ -24,8 +24,8 @@ local TASK_NAME = "lora2_task"
 local spi_id = 0 -- SPI接口ID
 local pin_cs = 8 -- 片选引脚
 local pin_reset = 1 -- 复位控制引脚
-local pin_busy = 31 -- 忙状态指示引脚
-local pin_dio1 = 32 -- DIO1引脚
+local pin_busy = 16 -- 忙状态指示引脚
+local pin_dio1 = 17 -- DIO1引脚
 
 local RECEIVE_TIMEOUT = 3000  -- 接收超时时间3秒
 

+ 5 - 5
module/Air780EPM/demo/lora2/readme.md

@@ -38,7 +38,7 @@
 
 3、Air780EPM 核心板通过SPI接口与lora模块连接并进行通信,具体接线如下:
 
-![image](https://docs.openLuat.com/cdn/image/Air780EXX_lora2.jpg)
+![image](https://docs.openLuat.com/cdn/image/Air780EPM_lora.jpg)
 
 | Air780EPM核心板  |  lora模块          |
 | --------------- | ----------------- |
@@ -49,8 +49,8 @@
 | 84/SPI0MISO     | MISO              |
 | 83/SPI0CS       | CSS               |
 | 22/GPIO1        | RST               |
-| 32/GPIO31       | BUSY              |
-| 33/GPIO32       | DIO1              |
+| 97/GPIO16       | BUSY              |
+| 100/GPIO17       | DIO1              |
 
 硬件连接注意事项:
 
@@ -60,9 +60,9 @@
 2. 控制信号连接:
    - RST引脚:连接至核心板的GPIO1,用于lora模块复位控制
   
-   - BUSY引脚:连接至核心板的GPIO31,用于lora模块忙状态指示
+   - BUSY引脚:连接至核心板的GPIO16,用于lora模块忙状态指示
   
-   - DIO1引脚:连接至核心板的GPIO32,用于lora模块中断信号接收
+   - DIO1引脚:连接至核心板的GPIO17,用于lora模块中断信号接收
   
 
 3. 连接确认:

+ 169 - 0
module/Air8101/demo/lora2/lora2_main.lua

@@ -0,0 +1,169 @@
+--[[
+@module  lora2_main
+@summary lora2功能测试主模块
+@version 1.0
+@date    2025.11.23
+@author  王世豪
+@usage
+本功能模块演示的内容为:
+1. lora2设备初始化与配置
+2. SPI接口初始化
+3. 发送和接收参数配置
+4. 事件回调处理
+5. 发送和接收任务管理
+
+本文件没有对外接口,直接在main.lua中require "lora2_main"就可以加载运行;
+]]
+
+-- 加载依赖模块
+local lora2_sender = require "lora2_sender"
+local lora2_receiver = require "lora2_receiver"
+
+local TASK_NAME = "lora2_task"
+
+local spi_id = 1 -- SPI接口ID
+local pin_cs = 3 -- 片选引脚
+local pin_reset = 6 -- 复位控制引脚
+local pin_busy = 7 -- 忙状态指示引脚
+local pin_dio1 = 8 -- DIO1引脚
+
+local RECEIVE_TIMEOUT = 3000  -- 接收超时时间3秒
+
+--[[
+event值有:
+    tx_done     -- 发送完成:数据已成功发送
+    rx_done     -- 接收完成:成功接收到数据
+    rx_timeout  -- 接收超时:在指定时间内未收到数据
+    rx_error    -- 接收错误:接收过程中发生错误
+__]]
+function callback(lora_device, event, data, size)
+    if event == "tx_done" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "tx_done")
+    elseif event == "rx_done" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_done", data, size)
+    elseif event == "rx_timeout" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_timeout")
+    elseif event == "rx_error" then
+        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_error")
+    else
+        log.warn("未知事件类型:", event)
+    end
+end
+
+local function lora2_init()
+    -- 初始化SPI
+    spi_lora = spi_lora or spi.deviceSetup(spi_id,pin_cs,0,0,8,10*1000*1000,spi.MSB,1,0)
+    if not spi_lora then
+        log.error("spi_lora init failed")
+        return false
+    end
+
+    -- 初始化LORA2设备
+    -- 当前支持型号:llcc68, sx1262
+    lora_device = lora_device or lora2.init("llcc68",{res = pin_reset,busy = pin_busy,dio1 = pin_dio1},spi_lora)
+    if not lora_device then
+        log.error("lora_device init failed")
+        return false
+    end
+    log.info("lora_device",lora_device)
+
+    -- 设置频道频率为433MHz
+    lora_device:set_channel(433000000) 
+
+    -- 配置 lora 设备的发送参数
+    lora_device:set_txconfig({
+        mode=1, 
+        power=22,
+        fdev=0,
+        bandwidth=0,
+        datarate=9,
+        coderate=4,
+        preambleLen=8,
+        fixLen=false,
+        crcOn=true,
+        freqHopOn=0,
+        hopPeriod=0,
+        iqInverted=false
+    })
+
+    -- 配置 lora 设备的接收参数
+    lora_device:set_rxconfig({
+        mode=1,
+        bandwidth=0,
+        datarate=9,
+        coderate=4,
+        bandwidthAfc=0,
+        preambleLen=8,
+        symbTimeout=0,
+        fixLen=false,
+        payloadLen=0,
+        crcOn=true,
+        freqHopOn=0,
+        hopPeriod=0,
+        iqInverted=false,
+        rxContinuous=false
+    })
+
+    return true
+end
+
+local function lora2_main_task_func()
+    local result,msg
+
+    while true do
+        result = lora2_init()
+        if not result then
+            log.info("lora2_init error")
+            goto EXCEPTION_PROC
+        end
+
+        -- 注册回调
+        lora_device:on(callback)
+
+        -- 默认初始化后启动接收
+        lora_device:recv(RECEIVE_TIMEOUT)
+
+        --- 发送设备就绪事件,将lora_device传递给sender模块
+        sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "DEVICE_READY", lora_device) 
+
+        while true do
+            msg = sys.waitMsg(TASK_NAME, "LORA_EVENT")
+
+            if msg[2]== "tx_done" then
+                log.info("lora2_main", "发送完成")
+                -- 通知sender模块发送完成
+                sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "TX_DONE")
+                -- 发送完成后启动接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+            elseif msg[2]== "rx_done" then
+                log.info("lora2_main", "接收完成", "数据长度:", msg[4])
+                -- 交由receiver模块处理数据
+                lora2_receiver.proc(msg[3], msg[4], lora_device)
+                -- 处理完成后启动接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+            elseif msg[2]== "rx_timeout" then
+                log.info("lora2_main", "接收超时")
+                -- 接收超时后继续接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+
+                -- 接收过程中发生错误
+            elseif msg[2]== "rx_error" then
+                log.info("lora2_main", "接收错误")
+                -- 接收错误后继续接收
+                lora_device:recv(RECEIVE_TIMEOUT)
+            end
+        end
+        
+        -- 出现异常
+        ::EXCEPTION_PROC::
+
+        -- 清空此task绑定的消息队列中的未处理的消息
+        sys.cleanMsg(TASK_NAME)
+
+        sys.wait(5000)
+    end
+end
+
+sys.taskInitEx(lora2_main_task_func, TASK_NAME)

+ 25 - 0
module/Air8101/demo/lora2/lora2_receiver.lua

@@ -0,0 +1,25 @@
+--[[
+@module  lora2_receiver
+@summary lora数据接收应用功能模块
+@version 1.0
+@date    2025.11.23
+@author  王世豪
+@usage
+本文件为lora数据接收应用功能模块,核心业务逻辑为:
+1. 处理接收到的lora数据。
+
+本文件的对外接口有:
+1. lora2_receiver.proc(data): 处理接收到的lora数据。
+2. sys.publish("LORA_RECV_DATA", data): 发布收到的数据给其他模块处理。
+]]
+
+local lora2_receiver = {}
+
+-- 处理接收到的lora数据
+function lora2_receiver.proc(data, size)
+    log.info("lora2_receiver", "收到数据", size, data)
+    -- 发布数据给其他模块
+    sys.publish("LORA_RECV_DATA", data)
+end
+
+return lora2_receiver

+ 131 - 0
module/Air8101/demo/lora2/lora2_sender.lua

@@ -0,0 +1,131 @@
+--[[
+@module  lora2_sender
+@summary lora数据发送应用功能模块
+@version 1.0
+@date    2025.11.23
+@author  王世豪
+@usage
+本文件为lora数据发送应用功能模块,核心业务逻辑为:
+1、订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
+2、lora2_sender task接收"DEVICE_READY"、"SEND_REQ"、"TX_DONE"消息,处理队列中的数据;
+    "DEVICE_READY"消息表示lora设备已初始化完成,"SEND_REQ"消息表示有新数据需要发送,"TX_DONE"消息表示数据发送完成;
+4、数据发送完成后通过回调函数通知发送方。
+
+本文件的对外接口有:
+1. sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func); 订阅"SEND_DATA_REQ"消息;
+    其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据、回调函数和回调参数一起publish出去;
+]]
+
+local lora2_sender = {}
+
+--[[
+数据发送队列,数据结构为:
+{
+    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
+    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
+}
+data: 要发送的数据,string类型,必须存在;
+cb.func: 数据发送结果的用户回调函数,可以不存在;
+cb.para: 数据发送结果的用户回调函数参数,可以不存在;
+]]
+
+local send_queue = {}
+
+lora2_sender.TASK_NAME = "lora2_sender"
+
+-- "SEND_DATA_REQ"消息的处理函数
+local function send_data_req_proc_func(tag, data, cb)
+    -- 将数据插入到发送队列send_queue中
+    table.insert(send_queue, {data=data, cb=cb})
+    -- 发送消息通知 lora sender task,有新数据等待发送
+    sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "SEND_REQ") 
+    log.info("队列", #send_queue)
+end
+
+-- 按照顺序发送send_queue中的数据
+-- 发送请求提交后,返回当前正在发送的数据项,等待发送完成事件
+-- 如果设备未初始化,则通知回调函数发送失败,并继续处理下一条
+local function send_item_func(lora_device)
+    local item
+    -- 如果发送队列中有数据等待发送
+    while #send_queue>0 do
+        -- 取出来第一条数据赋值给item
+        -- 同时从队列send_queue中删除这一条数据
+        item = table.remove(send_queue, 1)
+
+        -- 检查设备是否初始化
+        if not lora_device then
+            log.error("lora2_sender", "设备未初始化")
+            -- 通知回调函数发送失败
+            if item.cb and item.cb.func then
+                item.cb.func(false, item.cb.para)
+            end
+            return nil
+        end
+        
+        -- 发送数据
+        lora_device:send(item.data)
+        
+        -- 返回当前发送项,等待发送完成"TX_DONE"事件
+        return item
+    end
+    return nil
+end
+
+-- 处理发送结果的回调函数
+local function send_item_cbfunc(item, result)
+    if item then
+        -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
+        if item.cb and item.cb.func then
+            item.cb.func(result, item.cb.para)
+        end
+    end
+end
+
+-- lora client sender的任务处理函数
+local function lora2_sender_task_func()
+    local lora_device
+    local send_item = nil
+    local msg
+
+    while true do
+        -- 等待"LORA_EVENT"消息
+        msg = sys.waitMsg(lora2_sender.TASK_NAME, "LORA_EVENT")
+        log.info("lora2_sender", "收到消息", msg[2])
+        
+        -- 设备就绪事件
+        if msg[2] == "DEVICE_READY" then
+            lora_device = msg[3]
+            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
+            if lora_device and not send_item then
+                send_item = send_item_func(lora_device)
+            end
+
+        -- 发送数据请求
+        elseif msg[2] == "SEND_REQ" then
+            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
+            if lora_device and not send_item then
+                send_item = send_item_func(lora_device)
+            end
+        -- 发送完成事件
+        elseif msg[2] == "TX_DONE" then
+            -- 通知回调函数发送成功
+            send_item_cbfunc(send_item, true)
+            -- 清空当前发送项
+            send_item = nil
+            -- 继续处理队列中的下一条数据
+            send_item = send_item_func(lora_device)
+        end
+    end
+end
+
+-- 订阅"SEND_DATA_REQ"消息;
+-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
+-- 本demo项目中uart_app.lua中publish了这个消息;
+sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
+
+--创建并且启动一个task
+--运行这个task的处理函数lora2_sender_task_func
+sysplus.taskInitEx(lora2_sender_task_func, lora2_sender.TASK_NAME)
+
+return lora2_sender

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

@@ -0,0 +1,65 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0.0
+@date    2025.11.23
+@author  王世豪
+@usage
+本demo演示的功能为:
+1. lora2设备初始化与配置(SPI接口初始化、lora芯片配置)
+2. lora2无线数据发送与接收功能
+3. 串口与lora数据互转功能
+4. 事件回调机制与任务管理
+5. 数据缓冲区管理与超时处理
+
+]]
+--[[
+必须定义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 = "LORA2_DEMO"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    wdt.init(9000) -- 初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+-- 加载ADC测量功能模块
+require "lora2_main"
+
+-- 加载串口功能模块
+require "uart_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!
+
+

+ 116 - 0
module/Air8101/demo/lora2/readme.md

@@ -0,0 +1,116 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、lora2_main.lua:lora主应用功能模块;
+
+3、lora2_sender.lua:lora数据发送应用功能模块;
+
+4、lora2_receiver.lua:lora数据接收应用功能模块;
+
+5、uart_app.lua:UART应用功能模块;
+
+
+## 演示功能概述
+
+使用Air8101核心板+lora模块测试lora的数据发送和接收功能。
+
+需要两套Air8101核心板+lora模块,才能进行有效的通信测试。
+
+## 演示硬件环境
+
+1、Air8101 核心板两块 + 同型号lora模块两块(芯片选择llcc68/sx1268);
+
+本演示中,使用的lora模块为Ai-Thinker的Ra-01SC模块,该模块基于llcc68芯片。
+
+![image](https://docs.openLuat.com/cdn/image/Ai-Thinker_lora模块.png)
+
+[淘宝购买链接 点击此处](https://detail.tmall.com/item.htm?ali_refid=a3_430673_1006%3A1310580056%3AN%3AbqpGoPHh6maprV%2FiOvfLQlttIGZ5%2F0H4%3A8ac800110c31e390e3a00be4332367ec&ali_trackid=1_8ac800110c31e390e3a00be4332367ec&id=642897064381&mi_id=00007gKI69Jf4rfCAQlHSi0FwOhhsjkApkWKIk0aR6x63s4&mm_sceneid=1_0_1079550197_0&priceTId=2147872c17636209574944284e0e8c&skuId=4696650265078&spm=a21n57.sem.item.2&utparam=%7B%22aplus_abtest%22%3A%2204f30be1eb4a5e73cebb07691a717fb3%22%7D&xxc=ad_ztc)
+
+2、TYPE-C USB数据线一根
+
+3、Air8101核心板和数据线的硬件接线方式为
+
+- Air8101核心板通过TYPE-C USB口供电;(核心板背面的功耗测试开关拨到OFF一端)
+
+- 如果测试发现软件频繁重启,重启原因值为:poweron reason 0,可能是供电不足,此时再通过直流稳压电源对核心板的vbat管脚进行4V供电,或者VIN管脚进行5V供电;
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- USB转串口数据线,一般来说,白线连接核心板的UART1_TX,绿线连接核心板的UART1_RX,黑线连接核心板的GND,另外一端连接电脑USB口;
+
+3、Air8101 核心板通过SPI接口与lora模块连接并进行通信,具体接线如下:
+
+![image](https://docs.openLuat.com/cdn/image/Air8101_lora.jpg)
+
+| Air8101核心板    |  lora模块          |
+| --------------- | ----------------- |
+| 3V3             | VCC               |
+| GND             | GND               |
+| 65/SPI1_CLK     | SCK               |
+| 66/SPI1_CS      | CSS               |
+| 8/SPI1_MISO     | MISO              |
+| 67/SPI1_MOSI    | MOSI              |
+| 9/GPIO6         | RST               |
+| 10/GPIO7        | BUSY              |
+| 14/GPIO8        | DIO1              |
+
+硬件连接注意事项:
+
+1. 电源要求:
+   - 不同lora模块的供电电压范围可能存在差异,请根据具体模块规格确定合适的电源电压
+
+2. 控制信号连接:
+   - RST引脚:连接至Air8101的GPIO6,用于lora模块复位控制
+  
+   - BUSY引脚:连接至Air8101的GPIO7,用于lora模块忙状态指示
+  
+   - DIO1引脚:连接至Air8101的GPIO8,用于lora模块中断信号接收
+  
+
+3. 连接确认:
+   - 上述GPIO引脚分配已在测试中验证可用
+  
+   - 如有变更需求,需同步修改软件配置中的引脚定义
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8101 V2001版本固件(必须使用大于等于V2001版本的固件)](https://docs.openluat.com/air8101/luatos/firmware/)
+
+3、PC端的串口工具,例如SSCOM、LLCOM等都可以;
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、打开PC端的串口工具,选择对应的端口,配置波特率115200,数据位8,停止位1,无奇偶校验位;
+
+5、如下是Air8101核心板搭配lora模块发送和接收数据的演示结果:(Air8101核心板+lora模块,下面统称为lora设备)
+
+(1)上电后,lora设备A和B会进入lora接收状态,等待接收数据; 若接收超时,会继续等待接收, 所以在没有数据传输时,luatools会一直打印接收超时信息。
+
+```lua
+I/user.lora2_main 接收超时
+```
+
+(2)设备A发送数据,设备B接收数据:
+  
+   - 发送端通过UART1发送字符串"Hello, I am LoRa device A!"
+
+   - 接收端通过UART1成功接收到数据"Hello, I am LoRa device A!"
+  
+(2)设备B发送数据,设备A接收数据:
+  
+   - 发送端通过UART1发送字符串"Hello, I am LoRa device B!"
+
+   - 接收端通过UART1成功接收到数据"Hello, I am LoRa device B!"
+
+![image](https://docs.openLuat.com/cdn/image/lora_uart_test.png)
+
+```

+ 75 - 0
module/Air8101/demo/lora2/uart_app.lua

@@ -0,0 +1,75 @@
+--[[
+@module  uart_app
+@summary 串口应用功能模块
+@version 1.0
+@date    2025.11.23
+@author  王世豪
+@usage
+本文件为串口应用功能模块,核心业务逻辑为:
+1、打开uart1,波特率115200,数据位8,停止位1,无奇偶校验位;
+2、uart1和pc端的串口工具相连;
+3、从uart1接收到pc端串口工具发送的数据后,通过lora发送;
+4、收到lora数据后,将数据通过uart1发送到pc端串口工具;
+
+本文件的对外接口有两个:
+1、sys.publish("SEND_DATA_REQ", "uart", read_buf) ,通知lora2_sender 模块发送read_buf数据;
+2、sys.subscribe("LORA_RECV_DATA", recv_data_from_lora_proc),订阅LORA_RECV_DATA消息,处理消息携带的数据;
+]]
+
+
+-- 使用UART1
+local UART_ID = 1
+-- 串口接收数据缓冲区
+local read_buf = ""
+
+-- 末尾增加回车换行两个字符,通过uart发送出去,方便在PC端换行显示查看
+local function recv_data_from_lora_proc(data)
+    uart.write(UART_ID, data.."\r\n")
+end
+
+
+local function concat_timeout_func()
+    -- 如果存在尚未处理的串口缓冲区数据;
+    -- 将数据通过publish通知其他应用功能模块处理;
+    -- 然后清空本文件的串口缓冲区数据
+    if read_buf:len() > 0 then
+        sys.publish("SEND_DATA_REQ", "uart", read_buf)
+        read_buf = ""
+    end
+end
+
+
+-- UART1的数据接收中断处理函数,UART1接收到数据时,会执行此函数
+local function read()
+    local s
+    while true do
+        -- 非阻塞读取UART1接收到的数据,最长读取1024字节
+        s = uart.read(UART_ID, 1024)
+
+        -- 如果从串口没有读到数据
+        if not s or s:len() == 0 then
+            -- 启动50毫秒的定时器,如果50毫秒内没收到新的数据,则处理当前收到的所有数据
+            -- 这样处理是为了防止将一大包数据拆分成多个小包来处理
+            -- 例如pc端串口工具下发1100字节的数据,可能会产生将近20次的中断进入到read函数,才能读取完整
+            -- 此处的50毫秒可以根据自己项目的需求做适当修改,在满足整包拼接完整的前提下,时间越短,处理越及时
+            sys.timerStart(concat_timeout_func, 50)
+            -- 跳出循环,退出本函数
+            break
+        end
+
+        log.info("uart_app.read len", s:len())
+
+        -- 将本次从串口读到的数据拼接到串口缓冲区read_buf中
+        read_buf = read_buf..s
+    end
+end
+
+-- 初始化UART1,波特率115200,数据位8,停止位1
+uart.setup(UART_ID, 115200, 8, 1)
+
+-- 注册UART1的数据接收中断处理函数,UART1接收到数据时,会执行read函数
+uart.on(UART_ID, "receive", read)
+
+-- 订阅"LORA_RECV_DATA"消息的处理函数recv_data_from_lora_proc
+-- 收到"LORA_RECV_DATA"消息后,会执行函数recv_data_from_lora_proc
+sys.subscribe("LORA_RECV_DATA", recv_data_from_lora_proc)