主题
MQTT 客户端例程 2:订阅云端下发控制本地输出,并回 ACK
这一页只讲这一个例程。同组其余例程、以及这个库的函数说明都在 05-02-mqtt。
适用:同上 | 依赖模块:mqtt json plc | 难度:★☆☆
场景:云端向下发主题 lp3232/dev01/downlink 发 {"relay":1} 吸合继电器、{"set_temp":350} 改目标值,设备执行完往上报告主题回一条 ack,云端才知道指令真的落地了。订阅必须放在 CONNECTED 回调里 —— 未连接时 subscribe 会直接失败,而且断线重连后也要重新订阅。
完整代码(可直接下载运行):
lua
-- 云端下发 {"relay":1} / {"set_temp":350} → 写 Y0 / D200 → 回 ACK
local SUB = "lp3232/dev01/downlink"
local PUB = "lp3232/dev01/uplink"
mqttc = mqtt.creat("192.168.1.20", 1883, plc.plc_name(), "", "")
mqtt.reg(mqttc, mqtt.CONNECTED, function()
mqtt.subscribe(mqttc, SUB, 0) -- 连上再订阅,且每次重连都会重新订阅
end)
mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
local ok, cmd = pcall(json.decode, payload)
if not (ok and type(cmd) == "table") then
print("下发不是合法 JSON,已忽略:", payload)
return
end
if cmd.relay ~= nil then
plc.y(0, tonumber(cmd.relay) or 0)
print("执行 relay =", plc.y(0))
end
if cmd.set_temp ~= nil then
plc.d(200, tonumber(cmd.set_temp) or plc.d(200))
print("设定值改为", plc.d(200))
end
mqtt.publish(mqttc, PUB, json.encode({ ack = true, ts = os.time() }), 0, 0)
end)
mqtt.start(mqttc)要点:
RECVDATA要在start之前注册,否则 Broker 早早推来的消息会被漏掉。- 订阅放
CONNECTED回调有两层好处:规避未连接时的失败,以及每次重连自动补订阅(因为CONNECTED每次都会执行到)。 json.decode一定用pcall包住:云端可能推空串或纯文本,不捕获会让回调抛错。- ACK 发到上行主题而不是下发主题,避免订阅侧自己收到自己的回执形成回环。
- 不再关心的主题可以在
RECVDATA里return忽略,不必真的发取消订阅。
验证方法:
- 下载后订阅
lp3232/dev01/uplink,向下发主题lp3232/dev01/downlink发{"relay":1}。 - 设备日志出现
执行 relay = 1,Y0 吸合(上位机监视或万用表确认),uplink收到{"ack":true,"ts":...}。 - 发
{"set_temp":350}:日志打印设定值改为 350,D200 变成 350。 - 故意发一串非法内容(比如
hello):日志打印「下发不是合法 JSON」且程序继续跑(pcall生效)。 - 停掉 Broker 再恢复:重连后再次订阅自动完成,重新下发照样能执行。
注意事项:
mqtt.unsubscribe在 2026-08-16 之前的固件上会抛错(见 mqtt 库页);要兼容那批旧固件就别调用它,改用上面「在RECVDATA里忽略」的写法。- 回调在同一个线程里串行执行,里面不要做重活:收到指令只做「写寄存器 + 回 ACK」,具体动作交给梯形图。
- 下发字段用
tonumber(...) or 现值兜底,避免云端给个字符串把寄存器写坏。