主题
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为真,判断必须写== 1mqtt.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 编程手册。