developlink NuM
发布于

新增功能(配置里面可以直接实现补丁的功能)(固件尾缀版本必须要在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 作为复用模板,每次只更新 paramstime,避免重复构造表。
  • 发布时用 {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,含 topicpayload 字段;TCP 下行则直接是字符串。下行时把 topic 注入 JSON 一并回发串口,串口设备可知消息来源主题。
  • 上行解析串口 JSON,提取 topic 后用 {b = jdata.data, t = jdata.topic} 发布——data 字段原样作为 table 负载,框架序列化后发到指定主题。
  • 串口 ID 为 1(D_RECV_1 / D_SEND_1),根据实际硬件改 ID。

使用要点总结

  1. 任务包裹:每个独立任务用 function() ... end 包裹,作为补丁片段加载;多个任务可并列多个包裹。
  2. 数据源选规则:凡脚本接管收发的网络通道,上位机配置中数据源必须选"规则",否则框架自动透传造成重复。
  3. 自定义通道从 21 起:21 = 自定义1,22 = 自定义2,依次递增;串口为 1/2/3。
  4. msg 结构区分:网络 MQTT 下行是 {topic=, payload=};自定义通道上行是 {b=table, d=string, from=}
  5. 异步非阻塞:HTTP、MQTT 均为异步回调,不要在回调里做长时间阻塞操作;跨回调共享状态用 upvalue。
  6. 状态变化触发:轮询类任务用变量缓存上次状态,仅在跳变时动作,减少总线消息和设备动作。
浏览(28)
点赞(1)
收藏
评论
暂无数据