适用平台:SDRTU 系列网关(Lua 5.4.4 脚本框架)
文档类型:二次开发任务模板参考手册
SDRTU 应用层基于 Lua 5.4 脚本框架,通过高性能消息总线(类 eventbus)连接串口、网络、Modbus、自定义端点等功能模块。二次开发只需通过 sys.publish / sys.subscribe 完成消息流转,无需管理底层连接。
本文档收录 6 个标准任务模板,均采用统一的 function() ... end 任务包裹格式,可直接作为补丁片段组合使用。
| 主题 | 方向 | 说明 |
|---|---|---|
N_RECV_{SID} |
网络 → 脚本 | 从网络通道收到数据 |
N_SEND_{SID} |
脚本 → 网络 | 发送数据到网络通道 |
D_RECV_{SID} |
端点 → 脚本 | 从数据端点收到数据 |
D_SEND_{SID} |
脚本 → 端点 | 发送数据到数据端点 |
{SID} 为通道 ID:
| SID | 通道 |
|---|---|
| 1 / 2 / 3 | 串口 1 / 2 / 3 |
| 5 | 控制台 |
| 6 | 规则引擎(手动) |
| 21+ | 自定义通道(21 = 自定义1,依次递增) |
-- 形式1:纯字符串,走通道默认主题
sys.publish("N_SEND_1", "string payload")
-- 形式2:指定 MQTT 主题 + 字符串负载
sys.publish("N_SEND_1", {t = "/topic", d = "string payload"})
-- 形式3:指定 MQTT 主题 + table 负载(框架自动序列化)
sys.publish("N_SEND_1", {t = "/topic", b = {key = value}})
| 字段 | 类型 | 说明 |
|---|---|---|
t |
string | 指定发布的 MQTT 主题 |
d |
string | 字符串负载 |
b |
table | 二进制/table 负载 |
网络下行(N_RECV_x):
msg = {topic = "主题", payload = "字符串负载"}(table)msg 直接为字符串自定义通道上行(D_RECV_21 等):
msg = {
b = buff, -- table 格式数据
d = jbuff, -- string 格式数据(已编码)
from = "XX" -- 数据来源标识
}
规则引擎回调签名:function(data, id, from),from 常见取值:
| from | 含义 |
|---|---|
MB |
Modbus 采集数据 |
MB_REPLY |
Modbus 写回复 |
D6 |
645 协议数据 |
function() ... end 包裹,可并列多个。| 编号 | 模板 | 核心接口 | 适用场景 |
|---|---|---|---|
| 1 | 定时心跳上报 | sys.timerLoopStart / N_SEND_1 |
周期保活 |
| 2 | 断网联动继电器 | sock.isReady / D_SEND_21 |
状态监测与本地控制 |
| 3 | Modbus 数据 HTTP 上传 | D_RECV_21 / http.post |
采集数据转发第三方平台 |
| 4 | 动态 Token HTTP 上报 | http.post / 闭包状态 |
需鉴权的 HTTP 接口 |
| 5 | 自定义通道转 MQTT 指定主题 | D_RECV_21 / N_SEND_1 {t,d,b} |
数据格式转换 + 指定主题发布 |
| 6 | 串口 JSON 透传指定主题 | D_RECV_1 / N_RECV_1 |
串口设备动态指定 MQTT 主题 |
功能:每 15 秒向网络通道 1 发送心跳帧,维持平台在线判定。
配置前提:网络通道 1 已连接平台。
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(fn, ms) |
循环定时器,单位 ms,误差 < 10ms |
misc.getImei() |
设备 IMEI |
misc.getIccid() |
SIM 卡 ICCID |
os.time() |
Unix 时间戳(秒) |
注意事项:心跳帧格式 $HB,{json}\r\n 为平台约定,按实际平台协议调整前缀与结尾符。
功能:每 10 秒检测网络状态,网络断开时通过自定义通道 21 打开继电器 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() |
返回网络链路就绪状态(boolean) |
sys.publish("D_SEND_21", {DO1=1}) |
向自定义通道 1 写 DO1 参数,框架转 Modbus 写线圈 |
注意事项:
last_net 缓存上次状态,仅在状态跳变时动作,避免重复下发。DO1)必须与 Modbus 配置中的参数名一致。功能:订阅自定义通道 21 的 Modbus 采集数据,通过 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
接口说明:
| 接口 | 说明 |
|---|---|
http.post(url, opts, cb) |
异步 HTTP POST |
opts.timeout |
超时时间(秒) |
opts.type |
body 类型,2 = JSON |
opts.body |
请求体字符串 |
opts.header |
请求头,table.setHeader 构造 |
回调 (result, code, headers, body) |
code 为 HTTP 状态码 |
注意事项:自定义通道数据在 msg.b(table),直接 json.encode 即可。
功能:启动时申请 Token,Modbus 数据到达后携带 Bearer Token 通过 HTTP 上报;Token 缺失时自动补申请。
配置前提:同 4.3,另需 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 共享 | token 为闭包 upvalue,get_token 回调写入,上报回调读取 |
| 字段兼容 | 兼容 token / access_token / data.token / data.access_token 四种返回结构 |
| 缺失处理 | Token 缺失时跳过本次上报并触发申请,避免无 Token 请求 |
注意事项:生产环境建议增加 Token 有效期定时刷新(如 sys.timerLoopStart(get_token, 有效期*1000))。
功能:订阅自定义通道 21 数据,按平台模板重新包装 JSON,发布到指定 MQTT 主题;支持按 from 来源分发处理。
配置前提:
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 查 rules 表分发 |
| 模板复用 | template 表复用,每次更新 params 与 time(毫秒时间戳) |
| 指定主题发布 | {t = pubTopic, d = payload, b = params},t 决定 MQTT 发布主题 |
注意事项:时间戳 os.time() * 1000 为毫秒级,按平台要求调整为秒或毫秒。
功能:
{"topic":"xxx","data":...},解析后将 data 发布到 topic 指定的 MQTT 主题。topic 注入 JSON 后回发串口。配置前提:
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
接口说明:
| 方向 | 消息结构 | 处理 |
|---|---|---|
| 下行 N_RECV_1 | {topic=, payload=} |
解析 payload,注入 topic 字段,D_SEND_1 回发串口 |
| 上行 D_RECV_1 | JSON 字符串 | 提取 topic,{b = data, t = topic} 发布到指定主题 |
注意事项:
json.decode 返回 (table, result),result 为 true 表示解析成功。| API | 说明 |
|---|---|
sys.publish(topic, ...) |
发布消息 |
sys.subscribe(topic, fn) |
订阅消息,回调 function(data, id, from) |
sys.unsubscribe(topic, fn) |
取消订阅 |
sys.timerStart(fn, ms, ...) |
单次定时器 |
sys.timerLoopStart(fn, ms, ...) |
循环定时器 |
sys.wait(ms) / sys.waitUntil(topic, ms) |
协程内等待(仅 taskInit 内) |
sys.taskInit(fn, ...) |
创建协程任务 |
json.encode(t) / json.decode(s) |
JSON 编解码,decode 返回 (table, ok) |
http.post(url, opts, cb) |
异步 HTTP POST |
sock.isReady() |
网络链路状态 |
misc.getImei() / misc.getIccid() |
设备标识 |
io.getDtu() |
获取设备配置 table |
rtos.meminfo() |
内存信息 |
log.info/warn/error/debug(...) |
日志输出 |