init: Air780EPM 官方 LuatOS 项目代码基线

- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
wangfq
2026-08-31 08:53:49 +08:00
commit 173dd6874f
813 changed files with 125484 additions and 0 deletions
@@ -0,0 +1,129 @@
--[[
@module AirSHT30_1000
@summary AirSHT30_1000应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为AirSHT30_1000驱动配置文件,核心业务逻辑为:
1、打开AirSHT30_1000
2、读取温湿度数据;
本文件没有对外接口,直接require "AirSHT30_1000"就可以加载运行;
]]
-- 本文件中的主机是指I2C主机,具体指Air780EXX系列每个模组
-- 本文件中的从机是指I2C从机,具体指AirSHT30_1000配件板上的sht30温湿度传感器芯片
local AirSHT30_1000 =
{
-- i2c_id:主机的i2c id
}
-- 从机地址为0x44
local slave_addr = 0x44
-- 计算数据表data中所有数据元素的crc8校验值
local function crc8(data)
local crc = 0xFF
for i = 1, #data do
crc = bit.bxor(crc, data[i])
for j = 1, 8 do
crc = crc * 2
if crc >= 0x100 then
crc = bit.band(bit.bxor(crc, 0x31), 0xff)
end
end
end
return crc
end
-- 打开AirSHT30_1000
--i2c_idnumber类型;
-- 主机使用的I2C ID,用来控制AirSHT30_1000
-- 取值范围:仅支持0和1;
-- 如果没有传入此参数,则默认为0;
--返回值:成功返回true,失败返回false
function AirSHT30_1000.open(i2c_id)
--如果i2c_id为nil,则赋值为默认值0
if i2c_id==nil then i2c_id=0 end
--检查参数的合法性
if not (i2c_id == 0 or i2c_id == 1) then
log.error("AirSHT30_1000.open", "invalid i2c_id", i2c_id)
return false
end
AirSHT30_1000.i2c_id = i2c_id
--初始化I2C
if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
log.error("AirSHT30_1000.open", "i2c.setup error", i2c_id)
return false
end
return true
end
-- 读取温湿度数据;
-- 返回值:失败返回false
-- 成功返回两个值,第一个为摄氏温度值(number类型,例如23.6表示23.6摄氏度),第二个为百分比湿度值(number类型,例如67表示67%的湿度)
function AirSHT30_1000.read()
-- 发送启动测量命令(高精度)
i2c.send(AirSHT30_1000.i2c_id, slave_addr, {0x24, 0x00})
-- 等待测量完成(SHT30高精度测量需~15ms)
sys.wait(20)
-- 读取6字节数据(温度高/低 + CRC,湿度高/低 + CRC
local data = i2c.recv(AirSHT30_1000.i2c_id, slave_addr, 6)
-- 如果没有读取到6字节数据
if type(data)~="string" or data:len()~=6 then
log.error("AirSHT30_1000.read", "i2c.recv error")
return false
end
-- log.info("AirSHT30_1000.read", data:toHex())
--如果校验值正确
if crc8({data:byte(1), data:byte(2)}) == data:byte(3) and crc8({data:byte(4), data:byte(5)}) == data:byte(6) then
-- 提取原始温度值
local temp_raw = (data:byte(1) << 8) | data:byte(2)
-- 提取原始湿度值
local hum_raw = (data:byte(4) << 8) | data:byte(5)
-- 转换为实际值(根据SHT30数据手册公式)
local temprature = (-45 + 175 * temp_raw / 65535.0)
local humidity = (100 * hum_raw / 65535.0)
-- 打印输出结果(保留2位小数)
-- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f ℃", temprature))
-- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f %%RH", humidity))
return temprature, humidity
else
log.error("AirSHT30_1000.read", "crc error", i2c_id)
return false
end
end
-- 关闭AirSHT30_1000
-- 返回值:成功返回true,失败返回false
function AirSHT30_1000.close()
--close接口没有返回值,理论上不会关闭失败
i2c.close(AirSHT30_1000.i2c_id)
return true
end
return AirSHT30_1000
@@ -0,0 +1,93 @@
--[[
@module take_photo
@summary 摄像头拍照控制模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本模块负责摄像头拍照控制,核心业务逻辑为:
1、摄像头初始化:根据配置参数初始化gc032a摄像头硬件
2、拍照执行:接收拍照指令,执行拍照操作
3、数据返回:拍照成功后发布照片数据
4、资源管理:拍照完成后关闭摄像头释放资源
对外接口:
1、事件发布:
- "CAMERA_TAKE_PHOTO_RSP":拍照完成时发布,携带照片数据
2、消息订阅:
- "CAMERA_TAKE_PHOTO_REQ":等待拍照指令
使用说明:
1、摄像头配置:支持gc032a型号
2、工作模式:拍照模式(work_mode=0
3、数据存储:使用ZBUFF格式存储照片数据
4、GPIO配置:支持摄像头使能、电源控制和补光灯控制
]]
require "gc032a"
-- 引入excamera扩展库模块
local excamera = require "excamera"
--[[
拍照功能task
@function camera_take_photo_task
@local
@return nil
]]
local function camera_take_photo_task()
-- 定义变量用于存储操作结果和数据
local result, data
-- 无限循环,持续等待拍照事件
while true do
--[[
配置gc032a摄像头参数表
@table spi_camera_param
@field id string 摄像头型号,支持"gc032a"、"gc0310"、"bf30a2"
@field i2c_id number I2C总线编号
@field work_mode number 工作模式,0为拍照模式,1为扫描模式
@field save_path string 拍照结果存储路径,"ZBUFF"表示由excamera库管理
@field camera_pwr number 摄像头使能管脚GPIO号,无则填nil
@field camera_pwdn number 摄像头pwdn开关脚GPIO号,无则填nil
@field camera_light number 摄像头补光灯控制管脚GPIO号,无则填nil
]]
local spi_camera_param = {
id = "gc032a", -- SPI摄像头型号
i2c_id = 1, -- 模块上使用的I2C编号
work_mode = 0, -- 工作模式:0为拍照模式,1为扫描模式
save_path = "ZBUFF", -- 拍照结果存储路径
camera_pwr = 2, -- 摄像头使能管脚GPIO号
camera_pwdn = 5, -- 摄像头pwdn开关脚GPIO号
camera_light = nil -- 摄像头补光灯控制管脚GPIO号
}
-- 等待外部触发拍照事件(CAMERA_TAKE_PHOTO_REQ)
sys.waitUntil("CAMERA_TAKE_PHOTO_REQ")
-- 打印内存信息
log.info("mem.lua", rtos.meminfo())
log.info("mem.sys", rtos.meminfo("sys"))
-- 初始化摄像头,传入配置参数
result = excamera.open(spi_camera_param)
-- 记录摄像头初始化状态
log.info("camera_take_photo_task excamera.open", result)
-- 判断摄像头初始化是否成功,不成功则直接关闭,成功则启动拍照
if result then
-- 执行拍照操作
result, data = excamera.photo()
log.info("camera_take_photo_task excamera.photo", result, type(data), data:used())
end
-- 发布拍照结果响应消息,携带操作结果和数据
sys.publish("CAMERA_TAKE_PHOTO_RSP", result, data)
-- 关闭摄像头,保留zbuff内存资源
excamera.close(true)
end
end
-- 创建拍照功能任务
sys.taskInit(camera_take_photo_task)
@@ -0,0 +1,143 @@
--[[
@module app_lpuart
@summary UART功能应用模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本模块负责低功耗UART应用,核心业务逻辑为:
1、初始化uart参数,设置波特率为9600,数据位8位,无校验位,1位停止位
2、配置uart发送与接收回调函数,用于处理发送完成和接收数据
3、对接收到的数据进行检测,当收到A0001指令时发布“CAMERA_TAKE_PHOTO_REQ”消息,通知拍照模块(例如app_camera.lua)执行拍照操作
4、拍照模块执行完拍照操作后,发布“CAMERA_TAKE_PHOTO_RSP”消息,携带操作结果和数据
5、收到“CAMERA_TAKE_PHOTO_RSP”消息后,根据操作结果和数据,判断是否需要将图像数据发送出去
6、需要将图像数据通过UART发送时,先关闭当前UART,重新设置波特率为115200,发送数据,发送完成后关闭UART,重新设置波特率为9600
]]
-- 使用UART1
local UART_ID = 1
-- 串口接收数据缓冲区
local read_buf = ""
-- false表示空闲,true表示正常处理拍照回传业务
local is_busy = false
-- 串口发送完成回调函数
local function uart_sent_callback()
sys.publish("SEND_PHOTO_DONE")
end
-- 处理拍照结果响应消息任务
local function camera_take_photo_rsp_task(data)
log.info("camera_take_photo_rsp_task", type(data), data:used())
local uart_tx_start = 0 -- 已发送数据的起始位置
local uart_tx_len = 102400 -- 每次发送的数据长度
uart.close(UART_ID)
uart.setup(UART_ID, 115200, 8, 1)
-- 循环发送数据,直到所有数据都发送完成
while true do
if data:used() - uart_tx_start >= 102400 then
uart_tx_len = 102400
elseif data:used() - uart_tx_start < 102400 then
uart_tx_len = data:used() - uart_tx_start
elseif data:used() - uart_tx_start == 0 then
break
end
uart.tx(UART_ID, data, uart_tx_start, uart_tx_len)
uart_tx_start = uart_tx_start + uart_tx_len
-- 等待发送完成(最多等待30秒)
local result = sys.waitUntil("SEND_PHOTO_DONE", 30 * 1000)
if not result then
log.error("uart.tx timeout")
break
end
end
uart.close(UART_ID)
uart.setup(UART_ID, 9600, 8, 1)
is_busy = false
end
-- 发布拍照结果响应消息
local function camera_take_photo_rsp(result, data)
if result then
log.info("camera_take_photo_rsp", result, type(data), data:used(), is_busy)
sys.taskInit(camera_take_photo_rsp_task, data)
else
log.info("camera_take_photo_rsp", result)
is_busy = false
end
end
-- 处理串口缓冲区数据超时函数
-- 防止将一大包数据拆分成多个小包来处理
local function concat_timeout_func()
log.info("concat_timeout_func", read_buf:len(), is_busy)
-- 如果存在尚未处理的串口缓冲区数据;
-- 将数据通过publish通知其他应用功能模块处理;
-- 然后清空本文件的串口缓冲区数据
if read_buf:len() > 0 then
if not is_busy then
-- 检查是否包含拍照指令
if read_buf:find("A0001") then
sys.publish("CAMERA_TAKE_PHOTO_REQ")
is_busy = true
end
end
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())
-- log.info("uart_app.read", s)
-- 将本次从串口读到的数据拼接到串口缓冲区read_buf中
read_buf = read_buf..s
end
end
-- 初始化UART1,波特率115200,数据位8,停止位1
uart.setup(UART_ID, 9600, 8, 1)
-- 注册UART1的数据接收中断处理函数,UART1接收到数据时,会执行read函数
uart.on(UART_ID, "receive", read)
-- 注册串口发送回调
uart.on(UART_ID, "sent", uart_sent_callback)
-- 订阅拍照结果响应消息
sys.subscribe("CAMERA_TAKE_PHOTO_RSP", camera_take_photo_rsp)
@@ -0,0 +1,287 @@
--[[
@module mqtt_main
@summary mqtt client 主应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为mqtt client 主应用功能模块,核心业务逻辑为:
1、创建一个mqtt client,连接server
2、处理连接/订阅/取消订阅/异常逻辑,出现异常后执行重连动作;
3、调用mqtt_receiver的外部接口mqtt_receiver.proc,对接收到的publish数据进行处理;
4、调用sys.sendMsg接口,发送"CONNECT OK"、"PUBLISH OK"和"DISCONNECTED"三种类型的"MQTT_EVENT"消息到mqtt_sender的task,控制publish数据发送逻辑;
5、收到MQTT心跳应答后,执行sys.publish("FEED_NETWORK_WATCHDOG") 对网络环境检测看门狗功能模块进行喂狗;
本文件没有对外接口,直接在main.lua中require "mqtt_main"就可以加载运行;
]]
-- 加载mqtt client数据接收功能模块
local mqtt_receiver = require "app_mqtt_receiver"
-- 加载mqtt client数据发送功能模块
local mqtt_sender = require "app_mqtt_sender"
-- mqtt服务器地址和端口
-- 这里使用的地址和端口,会不定期重启或维护,仅能用作测试用途,不可商用,说不定哪一天就关闭了
-- 用户开发项目时,替换为自己的商用服务器地址和端口
local SERVER_ADDR = "lbsmqtt.airm2m.com"
local SERVER_PORT = 1884
-- mqtt_main的任务名
local TASK_NAME = mqtt_sender.TASK_NAME_PREFIX.."main"
-- mqtt主题的前缀:IMEI号
local TOPIC_PREFIX = mobile.imei()
-- mqtt client的事件回调函数
local function mqtt_client_event_cbfunc(mqtt_client, event, data, payload, metas)
log.info("mqtt_client_event_cbfunc", mqtt_client, event, data, payload, json.encode(metas))
-- mqtt连接成功
if event == "conack" then
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "CONNECT", true)
-- 订阅单主题
-- 第二个参数表示qos,取值范围为0,1,2,如果不设置,默认为0
-- if not mqtt_client:subscribe(TOPIC_PREFIX .. "/down") then
-- sys.sendMsg(TASK_NAME, "MQTT_EVENT", "SUBSCRIBE", false, -1)
-- end
-- 订阅多主题,如果有需要,打开注释
-- 表中的每一个订阅主题的格式为[topic]=qos
-- if not mqtt_client:subscribe(
-- {
-- [(TOPIC_PREFIX .. "/data"]=0,
-- [(TOPIC_PREFIX .. "/cmd"]=1
-- }
-- ) then
-- sys.sendMsg(TASK_NAME, "MQTT_EVENT", "SUBSCRIBE", false, -1)
-- end
-- 订阅结果
-- data:订阅应答结果,true为成功,false为失败
-- payloadnumber类型;成功时表示qos,取值范围为0,1,2;失败时表示失败码,一般是0x80
elseif event == "suback" then
-- 发送消息通知 mqtt main task
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "SUBSCRIBE", data, payload)
-- 取消订阅成功
elseif event == "unsuback" then
-- 发送消息通知 mqtt main task
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "UNSUBSCRIBE", true)
-- 接收到服务器下发的publish数据
-- datastring类型,表示topic
-- payloadstring类型,表示payload
-- metastable类型,数据内容如下
-- {
-- qos: number类型,取值范围0,1,2
-- retainnumber类型,取值范围0,1
-- dupnumber类型,取值范围0,1
-- message_id: number类型
-- }
elseif event == "recv" then
-- 对接收到的publish数据处理
mqtt_receiver.proc(data, payload, metas)
-- 发送成功publish数据
-- datanumber类型,表示message id
elseif event == "sent" then
-- 发送消息通知 mqtt sender task
sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "PUBLISH_OK", data)
-- 服务器断开mqtt连接
elseif event == "disconnect" then
-- 发送消息通知 mqtt main task
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "DISCONNECTED", false)
-- 收到服务器的心跳应答
elseif event == "pong" then
-- 接收到数据,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
-- 严重异常,本地会主动断开连接
-- data:string类型,表示具体的异常,有以下几种:
-- "connect"tcp连接失败
-- "tx":数据发送失败
-- "conack"mqtt connect后,服务器应答CONNACK鉴权失败,失败码为payloadnumber类型)
-- "other":其他异常
elseif event == "error" then
if data == "connect" or data == "conack" then
-- 发送消息通知 mqtt main task,连接失败
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "CONNECT", false)
elseif data == "other" or data == "tx" then
-- 发送消息通知 mqtt main task,出现异常
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "ERROR")
end
end
end
-- mqtt main task 的任务处理函数
-- auto_reconnect:短连接还是长连接;长连接为true,表示断开连接后会自动重连;短连接为false,表示断开连接后,不会自动重连
local function mqtt_client_main_task_func(auto_reconnect)
local mqtt_client
local result, msg
while true do
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
while not socket.adapter(socket.dft()) do
log.warn("mqtt_client_main_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.info("mqtt_client_main_task_func", "recv IP_READY", socket.dft())
-- 清空此task绑定的消息队列中的未处理的消息
sys.cleanMsg(TASK_NAME)
-- 创建mqtt client对象
mqtt_client = mqtt.create(nil, SERVER_ADDR, SERVER_PORT)
-- 如果创建mqtt client对象失败
if not mqtt_client then
log.error("mqtt_client_main_task_func", "mqtt.create error")
goto EXCEPTION_PROC
end
-- 配置mqtt client对象的client idusernamepassword和clean session标志
result = mqtt_client:auth(TASK_NAME..mobile.imei(), "", "", true)
-- 如果配置失败
if not result then
log.error("mqtt_client_main_task_func", "mqtt_client:auth error")
goto EXCEPTION_PROC
end
-- 注册mqtt client对象的事件回调函数
mqtt_client:on(mqtt_client_event_cbfunc)
-- 设置mqtt keepalive时间为120秒
-- 如果没有设置,内核固件中默认为180秒
-- 有需要的话,可以打开注释
-- mqtt_client:keepalive(120)
-- 设置遗嘱消息,有需要的话,可以打开注释
-- mqtt_client:will(TOPIC_PREFIX .. "/status", "offline")
-- 连接server
result = mqtt_client:connect()
-- 如果连接server失败
if not result then
log.error("mqtt_client_main_task_func", "mqtt_client:connect error")
goto EXCEPTION_PROC
end
-- 连接、断开连接、订阅、取消订阅、异常等各种事件的处理调度逻辑
while true do
-- 等待"MQTT_EVENT"消息
msg = sys.waitMsg(TASK_NAME, "MQTT_EVENT")
log.info("mqtt_client_main_task_func waitMsg", msg[2], msg[3], msg[4])
-- connect连接结果
-- msg[3]表示连接结果,true为连接成功,false为连接失败
if msg[2] == "CONNECT" then
-- mqtt连接成功
if msg[3] then
log.info("mqtt_client_main_task_func", "connect success")
-- 通知mqtt sender数据发送应用模块的task,MQTT连接成功
sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "CONNECT_OK", mqtt_client)
-- mqtt连接失败
else
log.info("mqtt_client_main_task_func", "connect error")
-- 退出循环,发起重连
break
end
-- subscribe订阅结果
-- msg[3]表示订阅结果,true为订阅成功,false为订阅失败
elseif msg[2] == "SUBSCRIBE" then
-- 订阅成功
if msg[3] then
log.info("mqtt_client_main_task_func", "subscribe success", "qos: "..(msg[4] or "nil"))
-- 订阅失败
else
log.error("mqtt_client_main_task_func", "subscribe error", "code", msg[4])
-- 主动断开mqtt client连接
mqtt_client:disconnect()
-- 发送disconnect之后,此处延时1秒,给数据发送预留一点儿时间,发送到服务器;
-- 即使1秒的时间不足以发送给服务器也没关系;对服务器来说,mqtt客户端只是没有优雅的断开,不影响什么实质功能;
sys.wait(1000)
break
end
-- unsubscribe取消订阅成功
elseif msg[2] == "UNSUBSCRIBE" then
log.info("mqtt_client_main_task_func", "unsubscribe success")
-- 需要主动关闭mqtt连接
-- 用户需要主动关闭mqtt连接时,可以调用sys.sendMsg(TASK_NAME, "MQTT_EVENT", "CLOSE")
elseif msg[2] == "CLOSE" then
-- 主动断开mqtt client连接
mqtt_client:disconnect()
-- 发送disconnect之后,此处延时1秒,给数据发送预留一点儿时间,发送到服务器;
-- 即使1秒的时间不足以发送给服务器也没关系;对服务器来说,mqtt客户端只是没有优雅的断开,不影响什么实质功能;
-- sys.wait(1000)
break
-- 被动关闭了mqtt连接
-- 被网络或者服务器断开了连接
elseif msg[2] == "DISCONNECTED" then
break
-- 出现了其他异常
elseif msg[2] == "ERROR" then
break
end
end
-- 出现异常
::EXCEPTION_PROC::
-- 清空此task绑定的消息队列中的未处理的消息
sys.cleanMsg(TASK_NAME)
-- 通知mqtt sender数据发送应用模块的task,MQTT连接已经断开
sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "DISCONNECTED")
-- 如果存在mqtt client对象
if mqtt_client then
-- 关闭mqtt client,并且释放mqtt client对象
mqtt_client:close()
mqtt_client = nil
end
-- 根据是否需要自动重连标志,决定后续执行的动作
if auto_reconnect then
-- 5秒后跳转到循环体开始位置,自动发起重连
sys.wait(5000)
else
sys.publish("MQTT_CLIENT_CLOSE_RSP")
log.warn("mqtt_client_main_task_func", "exit")
break
end
end
-- 清除此task对应的管理表资源
sys.taskDel(TASK_NAME)
end
local function mqtt_client_run_req(connection_type)
--创建并且启动一个task
--运行这个task的处理函数mqtt_client_main_task_func
sys.taskInitEx(mqtt_client_main_task_func, TASK_NAME, nil, connection_type)
end
local function mqtt_client_close_req()
sys.sendMsg(TASK_NAME, "MQTT_EVENT", "CLOSE")
end
sys.subscribe("MQTT_CLIENT_RUN_REQ", mqtt_client_run_req)
sys.subscribe("MQTT_CLIENT_CLOSE_REQ", mqtt_client_close_req)
@@ -0,0 +1,63 @@
--[[
@module mqtt_receiver
@summary mqtt client数据接收处理应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为mqtt client 数据接收应用功能模块,核心业务逻辑为:
处理接收到的publish数据,同时将数据发送给其他应用功能模块做进一步处理;
本文件的对外接口有2个:
1、mqtt_receiver.proc(topic, payload, metas)publish数据处理入口,在mqtt_main.lua中调用;
2、sys.publish("RECV_DATA_FROM_SERVER", "recv from mqtt server: ", topic, payload)
将接收到的publish中的topic和payload数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
]]
local mqtt_receiver = {}
--[[
处理接收到的publish数据
@api mqtt_receiver.proc(topic, payload, metas)
@param1 topic string
表示publish主题
@param2 payload string
表示publish数据负载
@param2 payload string
表示publish数据负载
@param3 metas table
表示publish报文的一些参数;格式如下:
{
qos: number类型,取值范围0,1,2
retainnumber类型,取值范围0,1
dupnumber类型,取值范围0,1
message_id: number类型
}
@return1 result nil
@usage
mqtt_receiver.proc(topic, payload, metas)
]]
function mqtt_receiver.proc(topic, payload, metas)
log.info("mqtt_receiver.proc", topic, payload:len(), json.encode(metas))
-- 接收到数据,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
-- 将topic和payload通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
sys.publish("RECV_DATA_FROM_SERVER", "recv from mqtt server: ", topic, payload)
-- 也可以直接在此处编写代码,处理topic和payload
end
return mqtt_receiver
@@ -0,0 +1,155 @@
--[[
@module mqtt_sender
@summary mqtt client数据发送应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为mqtt client 数据发送应用功能模块,核心业务逻辑为:
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
2、mqtt sender task接收"CONNECT OK"、"PUBLISH_REQ"、"PUBLISH OK"三种类型的"MQTT_EVENT"消息,遍历队列send_queue,逐条发送数据到server
3、mqtt sender task接收"DISCONNECTED"类型的"MQTT_EVENT"消息,丢弃掉队列send_queue中未发送的数据;
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
本文件的对外接口有1个:
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的topic,payload和qos以及回调函数和回调参数一起publish出去;
本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
]]
local mqtt_sender = {}
--[[
数据发送队列,数据结构为:
{
[1] = {topic="topic1", payload="payload1", qos=0, cb={func=callback_function1, para=callback_para1}},
[2] = {topic="topic2", payload="payload2", qos=1, cb={func=callback_function2, para=callback_para2}},
[3] = {topic="topic3", payload="payload3", qos=2, cb={func=callback_function3, para=callback_para3}},
}
topic的内容为publish的主题,string类型,必须存在;
payload的内容为publish的负载数据,string类型,必须存在;
qos的内容为publish的质量等级,number类型,取值范围0,1,2,可选,如果用户没有指定,默认为0;
cb.func的内容为数据发送结果的用户回调函数,可以不存在;
cb.para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
]]
local send_queue = {}
-- mqtt client的任务名前缀
mqtt_sender.TASK_NAME_PREFIX = "mqtt_"
-- mqtt_client_sender的任务名
mqtt_sender.TASK_NAME = mqtt_sender.TASK_NAME_PREFIX.."sender"
-- "SEND_DATA_REQ"消息的处理函数
local function send_data_req_proc_func(tag, topic, payload, qos, cb)
-- 将原始数据增加前缀,然后插入到发送队列send_queue中
table.insert(send_queue, {topic=topic, payload="send from "..tag..": "..payload, qos=qos or 0, cb=cb})
-- 发送消息通知 mqtt sender task,有新数据等待发送
sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "PUBLISH_REQ")
end
-- 按照顺序发送send_queue中的数据
-- 如果调用publish接口成功,则返回当前正在发送的数据项
-- 如果调用publish接口失败,通知回调函数发送失败后,继续发送下一条数据
local function publish_item(mqtt_client)
local item
-- 如果发送队列中有数据等待发送
while #send_queue>0 do
-- 取出来第一条数据赋值给item
-- 同时从队列send_queue中删除这一条数据
item = table.remove(send_queue, 1)
-- publish数据
-- result表示调用publish接口的同步结果,返回值有以下几种:
-- 如果失败,返回nil
-- 如果成功,number类型,qos为0时直接返回0qos为1或者2时返回publish报文的message id
result = mqtt_client:publish(item.topic, item.payload, item.qos)
-- publish接口调用成功
if result then
return item
-- publish接口调用失败
else
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
if item.cb and item.cb.func then
item.cb.func(false, item.cb.para)
end
end
end
end
local function publish_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
-- mqtt client sender的任务处理函数
local function mqtt_client_sender_task_func()
local mqtt_client
local send_item
local result, msg
while true do
-- 等待"MQTT_EVENT"消息
msg = sys.waitMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT")
-- mqtt连接成功
-- msg[3]表示mqtt client对象
if msg[2] == "CONNECT_OK" then
mqtt_client = msg[3]
-- 发送send_queue中的数据
send_item = publish_item(mqtt_client)
-- mqtt publish数据请求
elseif msg[2] == "PUBLISH_REQ" then
-- 如果mqtt client对象存在,并且没有正在等待发送结果的发送数据项
if mqtt_client and not send_item then
-- 发送send_queue中的数据
send_item = publish_item(mqtt_client)
end
-- mqtt publish数据成功
elseif msg[2] == "PUBLISH_OK" then
-- publish成功,执行回调函数通知发送方
publish_item_cbfunc(send_item, true)
-- publish成功,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
-- 发送send_queue中的数据
send_item = publish_item(mqtt_client)
-- mqtt断开连接
elseif msg[2] == "DISCONNECTED" then
-- 清空mqtt client对象
mqtt_client = nil
-- 如果存在正在等待发送结果的发送项,执行回调函数通知发送方失败
publish_item_cbfunc(send_item, false)
-- 如果发送队列中有数据等待发送
while #send_queue>0 do
-- 取出来第一条数据赋值给send_item
-- 同时从队列send_queue中删除这一条数据
send_item = table.remove(send_queue,1)
-- 执行回调函数通知发送方失败
publish_item_cbfunc(send_item, false)
end
-- 当前没有正在等待发送结果的发送项
send_item = nil
end
end
end
-- 订阅"SEND_DATA_REQ"消息;
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
--创建并且启动一个task
--运行这个task的处理函数mqtt_client_sender_task_func
sys.taskInitEx(mqtt_client_sender_task_func, mqtt_sender.TASK_NAME)
return mqtt_sender
@@ -0,0 +1,48 @@
--[[
@module sht30_app
@summary sht30_app应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为sht30_app应用功能模块,核心业务逻辑为:
1、每隔1秒读取一次温湿度数据;
本文件没有对外接口,直接在main.lua中require "sht30_app"就可以加载运行;
]]
-- 加载AirSHT30_1000驱动文件
local air_sht30 = require "AirSHT30_1000"
-- 读取sht30温湿度数据任务
local function read_sht30_task()
--打开sht30硬件
air_sht30.open(1)
--读取温湿度数据
local temprature, humidity = air_sht30.read()
--读取结果
if temprature then
-- 打印输出结果(保留2位小数)
log.info("read_sht30_req", "temprature", string.format("%.2f ℃", temprature))
log.info("read_sht30_req", "humidity", string.format("%.2f %%RH", humidity))
else
log.error("read_sht30_task_func", "read error")
end
--关闭sht30硬件
air_sht30.close()
-- 发布读取结果
sys.publish("READ_SHT30_RSP", temprature~=nil, temprature, humidity)
end
-- 读取sht30数据请求消息处理函数
local function read_sht30_req()
sys.taskInit(read_sht30_task)
end
-- 订阅读取sht30数据请求消息
sys.subscribe("READ_SHT30_REQ", read_sht30_req)
@@ -0,0 +1,167 @@
--[[
@module tcp_client_main
@summary tcp client socket主应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为tcp client socket主应用功能模块,核心业务逻辑为:
1、创建一个tcp client socket,连接server
2、处理连接异常,出现异常后执行重连动作;
3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;
本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
]]
local libnet = require "libnet"
-- 加载tcp client socket数据接收功能模块
local tcp_client_receiver = require "app_tcp_receiver"
-- 加载tcp client socket数据发送功能模块
local tcp_client_sender = require "app_tcp_sender"
-- 电脑访问:https://iot.luatos.com/#/page6/netlab
-- 本工具使用方法可以参考:https://docs.openluat.com/common/TCPUDP_Test/
-- 点击 打开TCP 按钮,会创建一个TCP server
-- 将server的地址和端口赋值给下面这两个变量
local SERVER_ADDR = "115.120.239.161"
local SERVER_PORT = 25485
-- tcp_client_main的任务名
local TASK_NAME = tcp_client_sender.TASK_NAME
-- 处理未识别的消息
local function tcp_client_main_cbfunc(msg)
log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
end
-- tcp client socket的任务处理函数
-- auto_reconnect:短连接还是长连接;长连接为true,表示断开连接后会自动重连;短连接为false,表示断开连接后,不会自动重连
local auto_reconnect = true
local function tcp_client_main_task_func(auto_reconnect)
local socket_client
local result, para1, para2
while true do
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
while not socket.adapter(socket.dft()) do
log.warn("tcp_client_main_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.info("tcp_client_main_task_func", "recv IP_READY", socket.dft())
-- 创建socket client对象
socket_client = socket.create(nil, TASK_NAME)
-- 如果创建socket client对象失败
if not socket_client then
log.error("tcp_client_main_task_func", "socket.create error")
goto EXCEPTION_PROC
end
-- 配置socket client对象为tcp client
result = socket.config(socket_client)
-- 如果配置失败
if not result then
log.error("tcp_client_main_task_func", "socket.config error")
goto EXCEPTION_PROC
end
-- 连接server
result = libnet.connect(TASK_NAME, 30000, socket_client, SERVER_ADDR, SERVER_PORT)
-- 如果连接server失败
if not result then
log.error("tcp_client_main_task_func", "libnet.connect error")
goto EXCEPTION_PROC
end
log.info("tcp_client_main_task_func", "libnet.connect success")
-- 数据收发以及网络连接异常事件总处理逻辑
while true do
-- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
-- 如果处理失败,则退出循环
if not tcp_client_receiver.proc(socket_client) then
log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
break
end
-- 数据发送处理
-- 如果处理失败,则退出循环
if not tcp_client_sender.proc(TASK_NAME, socket_client) then
log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
break
end
-- 阻塞等待socket.EVENT事件或者15秒钟超时
-- 以下三种业务逻辑会发布事件:
-- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
-- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
-- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
result, para1, para2 = libnet.wait(TASK_NAME, 360000, socket_client)
log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
-- 如果连接异常或者用户主动关闭,则退出循环
if not result then
log.warn("tcp_client_main_task_func", "connection exception")
break
end
end
-- 出现异常
::EXCEPTION_PROC::
-- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
tcp_client_sender.exception_proc()
-- 如果存在socket client对象
if socket_client then
-- 关闭socket client连接
libnet.close(TASK_NAME, 5000, socket_client)
-- 释放socket client对象
socket.release(socket_client)
socket_client = nil
end
-- 根据是否需要自动重连标志,决定后续执行的动作
if auto_reconnect then
-- 5秒后跳转到循环体开始位置,自动发起重连
sys.wait(5000)
else
sys.publish("TCP_CLIENT_CLOSE_RSP")
log.warn("tcp_client_main_task_func", "exit")
break
end
end
-- 清除此task对应的管理表资源
sys.taskDel(TASK_NAME)
end
local function tcp_client_run_req(connection_type)
--创建并且启动一个task
--运行这个task的主函数tcp_client_main_task_func
sys.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc, connection_type)
end
local function tcp_client_close_req()
sys.sendMsg(TASK_NAME, socket.EVENT, "CLOSE_REQ")
end
sys.subscribe("TCP_CLIENT_RUN_REQ", tcp_client_run_req)
sys.subscribe("TCP_CLIENT_CLOSE_REQ", tcp_client_close_req)
@@ -0,0 +1,90 @@
--[[
@module tcp_client_receiver
@summary tcp client socket数据接收应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;
本文件的对外接口有2个:
1、tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用;
2、sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
]]
local tcp_client_receiver = {}
-- socket数据接收缓冲区
local recv_buff = nil
--[[
检查socket client是否收到数据,如果收到数据,读取并且处理完所有数据
@api tcp_client_receiver.proc(socket_client)
@param1 socket_client userdata
表示由socket.create接口创建的socket client对象;
必须传入,不允许为空或者nil
@return1 result bool
表示处理结果,成功为true,失败为false
@usage
--
tcp_client_receiver.proc(socket_client)
]]
function tcp_client_receiver.proc(socket_client)
-- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
if recv_buff==nil then
recv_buff = zbuff.create(1024)
-- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
-- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
-- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
end
-- 循环从内核的缓冲区读取接收到的数据
-- 如果读取失败,返回false,退出
-- 如果读取成功,处理数据,并且继续循环读取
-- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
while true do
-- 从内核的缓冲区中读取数据到recv_buff中
-- 如果recv_buff的存储空间不足,会自动扩容
local result = socket.rx(socket_client, recv_buff)
-- 读取数据失败
-- 有两种情况:
-- 1、recv_buff扩容失败
-- 2、socket client和server之间的连接断开
if not result then
log.error("tcp_client_receiver.proc", "socket.rx error")
return false
end
-- 如果读取到了数据, used()就必然大于0, 进行处理
if recv_buff:used() > 0 then
log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
-- 读取socket数据接收缓冲区中的数据,赋值给data
local data = recv_buff:query()
-- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
-- 接收到数据,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
-- 清空socket数据接收缓冲区中的数据
recv_buff:del()
-- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
else
break
end
end
return true
end
return tcp_client_receiver
@@ -0,0 +1,138 @@
--[[
@module tcp_client_sender
@summary tcp client socket数据发送应用功能模块
@version 1.0
@date 2026.02.12
@author 马梦阳
@usage
本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中;
2、tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server
3、tcp client socket和server之间的连接如果出现异常,tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据;
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;
本文件的对外接口有3个:
1、sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
2、tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用;
3、tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用;
]]
local tcp_client_sender = {}
local libnet = require "libnet"
--[[
数据发送队列,数据结构为:
{
[1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
[2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
}
data的内容为真正要发送的数据,必须存在;
func的内容为数据发送结果的用户回调函数,可以不存在
para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
]]
local send_queue = {}
-- tcp_client_main的任务名
tcp_client_sender.TASK_NAME = "tcp_client_main"
-- "SEND_DATA_REQ"消息的处理函数
local function send_data_req_proc_func(tag, data, cb)
-- 将原始数据增加前缀,然后插入到发送队列send_queue中
table.insert(send_queue, {data="send from "..tag..": "..data, cb=cb})
-- 通知tcp_client_main主任务有数据需要发送
-- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
sys.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
end
--[[
检查socket client是否需要发送数据,如果需要发送数据,读取并且发送完发送队列中的所有数据
@api tcp_client_sender.proc(task_name, socket_client)
@param1 task_name string
表示socket.create接口创建socket client对象时所处的task的name
必须传入,不允许为空或者nil
@param2 socket_client userdata
表示由socket.create接口创建的socket client对象;
必须传入,不允许为空或者nil
@return1 result bool
表示处理结果,成功为true,失败为false
@usage
tcp_client_sender.proc("tcp_client_main", socket_client)
]]
function tcp_client_sender.proc(task_name, socket_client)
local send_item
local result, buff_full
-- 遍历数据发送队列send_queue
while #send_queue>0 do
-- 取出来第一条数据赋值给send_item
-- 同时从队列send_queue中删除这一条数据
send_item = table.remove(send_queue,1)
-- 发送这条数据,超时时间15秒钟
result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
-- 发送失败
if not result then
log.error("tcp_client_sender.proc", "libnet.tx error")
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(false, send_item.cb.para)
end
return false
end
-- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
if buff_full then
log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
table.insert(send_queue, 1, send_item)
return true
end
log.info("tcp_client_sender.proc", "send success")
-- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(true, send_item.cb.para)
end
-- 发送成功,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
end
return true
end
--[[
socket client连接出现异常时,清空等待发送的数据,并且执行发送方的回调函数
@api tcp_client_sender.exception_proc()
@usage
tcp_client_sender.exception_proc()
]]
function tcp_client_sender.exception_proc()
-- 遍历数据发送队列send_queue
while #send_queue>0 do
local send_item = table.remove(send_queue,1)
-- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(false, send_item.cb.para)
end
end
end
-- 订阅"SEND_DATA_REQ"消息;
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
return tcp_client_sender