新增功能(配置里面可以直接实现补丁的功能)(固件尾缀版本必须要在143及其以上,例如 6.137.143,6.213.143,4.137.143)
SDRTU Lua 二次开发常用任务模板合集:定时上报、规则引擎、HTTP/MQTT 透传
背景
SDRTU 网关应用层运行 Lua 5.4 脚本框架,通过一套高性能消息总线(类 eventbus)连接串口、网络、Modbus、自定义端点等功能模块。开发者只需用 sys.publish / sys.subscribe 两个接口,就能把数据在各个通道间流转,无需关心底层连接管理。
本文整理 6 个高频场景的可直接复用模板,覆盖定时心跳、网络状态联动、HTTP 上报、动态 Token、MQTT 指定主题发布、串口 JSON 透传。所有模板均采用统一的 function() ... end 任务包裹写法,可替代原来的 userapp.lua 补丁增量下载。

核心机制:消息主题约定
框架内置通信主题,{SID} 为通道 ID:
| 方向 | 主题 | 说明 |
|---|---|---|
| 网络接收 | N_RECV_{SID} |
从网络通道收到数据 |
| 网络发送 | N_SEND_{SID} |
发送数据到网络通道 |
| 端点接收 | D_RECV_{SID} |
从数据端点收到数据(串口/自定义通道) |
| 端点发送 | D_SEND_{SID} |
发送数据到数据端点 |
通道 ID 约定:串口为 1/2/3,控制台 5,规则引擎 6,自定义通道从 21 开始(21 = 自定义1)。
网络发送支持三种参数形式:
sys.publish("N_SEND_1", "字符串数据") -- 纯字符串
sys.publish("N_SEND_1", {t = "主题", d = "字符串"}) -- 指定 MQTT 主题 + string
sys.publish("N_SEND_1", {t = "主题", b = table数据}) -- 指定 MQTT 主题 + table
t:指定发布的 MQTT 主题(string)d:字符串负载(string)b:二进制/table 负载,框架自动序列化
关键配置:凡脚本手动控制数据收发的网络通道,数据源必须选"规则",否则框架会自动透传导致重复发送。
模板 1:每 15 秒定时发送心跳
场景:设备需要周期性向平台上报心跳,维持在线状态判定。
function()
sys.timerLoopStart(function()
log.info("hb", "alive", misc.getIccid(), os.time())
data = {
imei = misc.getImei(),
deviceCode = misc.getIccid(),
time = os.time()
}
sys.publish("N_SEND_1", "$HB," .. json.encode(data) .. "\r\n")
end, 15000)
end
解析:
sys.timerLoopStart(callback, interval_ms)启动循环定时器,单位毫秒,误差小于 10ms。misc.getImei()/misc.getIccid()获取设备标识。- 心跳包用
$HB,前缀 + JSON +\r\n结尾,是平台常见的帧格式约定。 - 直接 publish 字符串到
N_SEND_1,走网络通道 1 默认主题上行。
模板 2:断网自动打开继电器
场景:网络断开时触发本地动作(如打开继电器 DO1),网络恢复后仅记录日志,实现"断网告警/联动"。
function()
-- 每 10 秒监测一次网络状态
-- 继电器通过自定义通道 D_SEND_21 控制,modbus 里面需要配置DO1的参数名称
local CHECK_INTERVAL = 10000
local last_net
local function check_status()
local net_ready = sock.isReady()
if net_ready == last_net then
return
end
last_net = net_ready
if net_ready then
log.info("relay", "net up")
else
log.warn("relay", "net down, open relay")
-- 通过自定义通道 21 打开继电器 DO1
sys.publish("D_SEND_21", {DO1 = 1})
end
end
sys.timerLoopStart(check_status, CHECK_INTERVAL)
log.info("status monitor started, interval=" .. CHECK_INTERVAL .. "ms")
end
解析:
sock.isReady()返回网络就绪状态(4G 入网/链路可用)。- 用
last_net缓存上一次状态,只在状态跳变时动作,避免每 10 秒重复发指令。 D_SEND_21是自定义通道 1 的发送主题,publish 一个 table{DO1 = 1},框架按 Modbus 配置中 DO1 的参数名映射到写线圈指令。- 前提:Modbus 配置中需定义 DO1 参数名称,且自定义通道绑定到对应 Modbus 主机。
模板 3:订阅 Modbus 数据并 HTTP 上传
场景:Modbus 采集到的数据经自定义通道 21 上报,脚本订阅后直接通过 HTTP POST 转发到业务平台。
function()
-- 订阅自定义通道 21,并通过 HTTP 转发收到的数据
local url = "http://example.com/api/upload" -- 替换为实际接口
--D_RECV_21 自定义1 通常是modbus 发布的数据,数据在 msg.b 中
sys.subscribe("D_RECV_21", function(msg)
if not msg or not msg.b then
log.warn("D_RECV_21 data is empty")
return
end
local body = json.encode(msg.b)
log.info("D_RECV_21 data", body)
http.post(url, {
timeout = 10,
type = 2,
body = body,
header = table.setHeader({["Content-Type"] = "application/json"})
}, function(_, code, _, response_body)
log.info("HTTP response", code, response_body)
end)
end)
end
解析:
- 自定义通道上报的 msg 结构为
{b = buff, d = jbuff, from = "XX"}:b是 table 数据,d是已编码的 string。 http.post(url, opts, callback)异步发起 POST,type = 2表示 JSON body。table.setHeader构造请求头表;回调参数依次为(result, code, headers, body)。- 网络通道数据源选"规则",HTTP 上报由脚本完全接管。
模板 4:动态获取 Token 后 HTTP 上报
场景:业务平台接口需要 Bearer Token 鉴权,Token 有有效期需动态申请。脚本启动时先取 Token,数据到达时若 Token 缺失则先申请再上报。
function()
-- 动态获取 Token,再将自定义通道 21 数据通过 HTTP 转发
local token_url = "http://example.com/api/token" -- 替换为 Token 接口
local report_url = "http://example.com/api/upload" -- 替换为上报接口
local token
local function get_token()
http.post(token_url, {
timeout = 10,
type = 2,
body = json.encode({}),
header = table.setHeader({["Content-Type"] = "application/json"})
}, function(_, code, _, response_body)
if tonumber(code) ~= 200 or not response_body then
log.warn("get token failed", code)
return
end
local result = json.decode(response_body)
token = result and (result.token or result.access_token
or (result.data and (result.data.token or result.data.access_token)))
log.info(token and "get token success" or "token is empty")
end)
end
sys.subscribe("D_RECV_21", function(msg)
if not msg or not msg.b then
log.warn("D_RECV_21 data is empty")
return
end
if not token then
get_token()
log.warn("token unavailable, skip HTTP push")
return
end
local body = json.encode(msg.b)
http.post(report_url, {
timeout = 10,
type = 2,
body = body,
header = table.setHeader({
["Content-Type"] = "application/json",
["Authorization"] = "Bearer " .. token
})
}, function(_, code, _, response_body)
log.info("HTTP response", code, response_body)
end)
end)
get_token()
end
解析:
token作为 upvalue 在闭包间共享,get_token成功后写入,数据上报时读取。- Token 字段兼容三种常见返回结构:
token/access_token/data.token。 - Token 缺失时跳过本次上报并触发申请,避免无 Token 请求刷接口;实际项目可加 Token 过期定时刷新。
- HTTP 异步回调更新 token,不阻塞消息总线。
模板 5:订阅自定义通道数据,转换格式推送到指定 MQTT 主题
场景:Modbus/自定义通道上报的原始字段需要重新包装成平台规定的 JSON 结构,并发布到特定 MQTT 主题(而非默认主题)。
function()
-- 订阅自定义通道1数据,修改格式后推送到指定MQTT主题示例
-- 自定义通道从21开始: 21 = 自定义1
-- D_RECV_21 接收自定义1的数据,N_SEND_1 发送到网络通道1
-- 网络通道数据源必须选"规则",否则会重复发送
local pubTopic = "/device/data/upload" -- 指定推送的MQTT主题
-- 上行数据模板
local template = {
version = "1.0",
type = "custom_data",
imei = misc.getImei(),
time = 0,
params = {}
}
-- 处理自定义通道1上报的数据
-- msg 格式: {b = buff, d = jbuff, from = "XX"}
-- b 是 table 格式数据,d 是 string 格式数据
local function msgPubHook(msg)
if not msg then return end
-- 从 msg.b 取出 table 数据
local src = msg.b or {}
local params = {}
for k, v in pairs(src) do
params[k] = v
end
-- 修改格式:包装到统一的上行模板
template.params = params
template.time = os.time() * 1000
local payload = json.encode(template)
log.info("custom pub", payload)
-- 推送到指定MQTT主题
-- t: 指定的发送主题(string)
-- d: string 数据
-- b: 二进制数据(table)
sys.publish("N_SEND_1", {t = pubTopic, d = payload, b = params})
end
-- 规则分发: 不同数据来源由不同函数处理
local rules = {
["MB"] = msgPubHook, -- Modbus 数据
["MB_REPLY"] = msgPubHook -- Modbus 回复
}
local function ruleWorker(data, id, from)
local callback = rules[from]
if callback and data then callback(data) end
end
-- 订阅自定义通道1的数据
sys.subscribe("D_RECV_21", ruleWorker)
end
解析:
ruleWorker(data, id, from)是规则引擎标准回调签名,第三个参数from标识数据来源(MB为 Modbus 采集数据,MB_REPLY为写回复)。- 用
rules分发表按来源路由到不同处理函数,便于扩展多来源。 template作为复用模板,每次只更新params和time,避免重复构造表。- 发布时用
{t = pubTopic, d = payload}指定 MQTT 主题;b同时传 table 供需要二进制的通道使用。
模板 6:串口 JSON 透传并指定 MQTT 主题
场景:串口设备发送 JSON {"topic":"xxx","data":...},网关解析后把 data 发布到 topic 指定的 MQTT 主题;下行 MQTT 消息则附带主题名回发串口,实现双向主题透传。
function()
-- 串口透传JSON指定主题
-- 网络通道数据源选规则,不能选串口
-- 串口发送: {"topic":"主题", "data":"内容"}
-- 网络收到数据下行, 解析payload, 加上topic下发到串口
sys.subscribe("N_RECV_1", function(msg)
if type(msg) == "table" and msg.topic and msg.payload then
local jdata, res = json.decode(msg.payload)
if res then
jdata.topic = msg.topic
sys.publish("D_SEND_1", json.encode(jdata))
end
end
end)
-- 串口接收JSON, 提取topic, 发送data到指定主题
sys.subscribe("D_RECV_1", function(msg)
local jdata, res = json.decode(msg)
if jdata and res and jdata.topic then
sys.publish("N_SEND_1", {b = jdata.data, t = jdata.topic})
log.info("send ok", jdata.topic)
else
log.info("send ERROR")
end
end)
end
解析:
- MQTT 下行消息 msg 是 table,含
topic和payload字段;TCP 下行则直接是字符串。下行时把topic注入 JSON 一并回发串口,串口设备可知消息来源主题。 - 上行解析串口 JSON,提取
topic后用{b = jdata.data, t = jdata.topic}发布——data字段原样作为 table 负载,框架序列化后发到指定主题。 - 串口 ID 为 1(
D_RECV_1/D_SEND_1),根据实际硬件改 ID。
使用要点总结
- 任务包裹:每个独立任务用
function() ... end包裹,作为补丁片段加载;多个任务可并列多个包裹。 - 数据源选规则:凡脚本接管收发的网络通道,上位机配置中数据源必须选"规则",否则框架自动透传造成重复。
- 自定义通道从 21 起:21 = 自定义1,22 = 自定义2,依次递增;串口为 1/2/3。
- msg 结构区分:网络 MQTT 下行是
{topic=, payload=};自定义通道上行是{b=table, d=string, from=}。 - 异步非阻塞:HTTP、MQTT 均为异步回调,不要在回调里做长时间阻塞操作;跨回调共享状态用 upvalue。
- 状态变化触发:轮询类任务用变量缓存上次状态,仅在跳变时动作,减少总线消息和设备动作。