Skip to content

3.3 方式二:Lua 脚本上云 ​

方式一(DTU 云交互)是配置驱动的,配置量小。以下情况需要改用脚本:

  • 上报前要换算(脉冲数折算成流量、原始 AD 值折算成工程量)
  • 只在满足条件时才上报(省流量、去抖动)
  • 一个云端点由多个寄存器算出来
  • 要对接第三方 MQTT 平台或 HTTP 接口,不经过云平台服务器

前三种用 plc.cloud_*(走服务器私有通道,和方式一同一条路);最后一种用 mqtt.* / http.*。

3.3.1 走服务器私有通道:plc.cloud_* ​

连接由「云平台」页的参数自动完成,脚本中无需处理连接和账号密码。

绑定寄存器(等价于方式一的可视化配置)

lua
plc.cloud_bind({
    -- D0 -> 云端数据点 "d0_value",16 位整数,仅上传
    { name = "d0_value", reg = "D0", type = plc.S16,  mode = plc.CLOUD_UP   },
    -- 云端点 "cmd_y0" -> Y0,位,仅下发
    { name = "cmd_y0",   reg = "Y0", type = plc.BITE, mode = plc.CLOUD_DOWN },
})

数据类型常量:plc.BITE(位)、plc.S16(16 位整数)、plc.S32(32 位整数)、plc.FLOAT(浮点)。

同步方向常量:plc.CLOUD_UP(仅上传)、plc.CLOUD_DOWN(仅下发)、plc.CLOUD_AUTO(双向,默认)。

地址进制不一致

cloud_bind 里 reg 写的 X/Y 按八进制换算("X10" = 第 8 点),和方式一的可视化配置一致;但函数式读写 plc.x(10) 是十进制第 10 点。同一个点在两处写法不同,需注意区分。

另外地址或类型写错的条目会被静默跳过、不报错,配置后建议在云端确认数据点已出现。

主动上报任意值(不受绑定表限制)

lua
plc.cloud_publish("流量", 12.5, "累计量", 3800)

参数成对出现(点名, 值),可以一次报多个。值在内部统一转成字符串。这个接口可用于上报计算结果,云端点不必对应某个真实寄存器:

lua
-- 每 5 秒把脉冲差值折算成瞬时流量再上报
local last = plc.dd(100)
local t = timer.start(5000, function()
    local now  = plc.dd(100)              -- 32 位累计脉冲
    local flow = (now - last) * 0.01 * 12  -- 折算成 m³/h
    last = now
    plc.cloud_publish("瞬时流量", flow)
end)

订阅云端下发

lua
plc.cloud_subscribe("cmd_y0", "设定温度")

plc.cloud_reg(plc.SUBSCRIBE, function(name, value)
    if name == "设定温度" then
        plc.d(200, math.floor(tonumber(value) or 0))
    end
end)

已经在 cloud_bind 里绑过寄存器的点,固件会自动写进对应寄存器,脚本无需在回调里处理。回调适合处理没有对应寄存器、需要脚本判断的下发。

3.3.2 定时器要用变量长期持有 ​

lua
-- 正确:用变量接住
t = timer.start(5000, function() ... end)

-- 错误:定时器对象会被 GC 回收,跑几次就停了
timer.start(5000, function() ... end)

timer.start(ms, callback, [once]),ms 最小 10,省略 once 是周期循环。回调跑在 Lua 主线程消息循环里,回调内不应执行长时间阻塞操作。

3.3.3 对接第三方 MQTT 平台 ​

不经过云平台服务器,直接连接第三方 Broker:

lua
-- 客户端对象也必须用变量长期持有
mqttc = mqtt.creat("broker.example.com", 1883, "lpplc_001", "user", "pass")
mqtt.set_keepalive(mqttc, 60)

mqtt.reg(mqttc, mqtt.CONNECTED, function()
    mqtt.subscribe(mqttc, "cmd/lpplc/y0", 1)   -- topic, qos
end)

mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
    local ok, cmd = pcall(json.decode, payload)   -- 外部数据一律 pcall
    if not ok or type(cmd) ~= "table" then return end
    if cmd.y0 ~= nil and cmd.y0 ~= json.null then
        plc.y(0, cmd.y0 and 1 or 0)
    end
end)

mqtt.start(mqttc)   -- 内部线程自动重连

t = timer.start(5000, function()
    if mqtt.is_linked(mqttc) == 1 then            -- 注意返回整数,必须 == 1
        mqtt.publish(mqttc, "data/lpplc/d0",
                     json.encode({ d0 = plc.d(0), ts = os.time() }), 1, 0)  -- qos, retain
    end
end)

三点需要注意:

  • mqtt.函数(客户端, ...) 与 客户端:函数(...) 两种写法等价,creat 只能用库表形式
  • mqtt.is_linked() 返回的是整数 0/1。Lua 里 0 为真,判断必须写 == 1
  • mqtt.publish() 在未连接时返回 nil(不是 false),判断失败要用 if not mqtt.publish(...) then

MQTT 模块只支持明文,不支持 TLS,连不上要求 8883 加密端口的公有云。需要加密只能自己在 payload 层做。

3.3.4 对接第三方 HTTP 接口 ​

lua
-- 带自定义头必须用对象模式
hc = http.creat("https://api.example.com/report")
hc:add_head("Content-Type", "application/json")
hc:add_head("X-Device-Id", "lpplc_001")

t = timer.start(60000, function()
    local body = json.encode({ d0 = plc.d(0), ts = os.time() })
    -- 定时器回调内应使用异步接口
    hc:post_asy("https://api.example.com/report", body, function(errcode, data, len)
        if errcode == 0 then
            -- data 是响应体字符串
        end
    end)
end)

简单场景可以直接 http.get(url) / http.post(url, body),但同步接口固定 15 秒超时且会阻塞整个 Lua 事件循环,定时器回调内应使用 get_asy / post_asy。

3.3.5 怎么选 ​

需求用什么
寄存器原值上云,不做处理方式一的可视化配置,无需脚本
上报前要换算/判断/合并多个寄存器plc.cloud_publish
云端下发要脚本参与判断plc.cloud_subscribe + plc.cloud_reg
数据要发给第三方物联网平台mqtt.*
数据要 POST 给对方的 Web 接口http.*,异步接口

Lua 各模块的完整 API 见 Lua 编程手册。


LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研