Skip to content

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 忽略,不必真的发取消订阅。

验证方法:

  1. 下载后订阅 lp3232/dev01/uplink,向下发主题 lp3232/dev01/downlink 发 {"relay":1}。
  2. 设备日志出现 执行 relay = 1,Y0 吸合(上位机监视或万用表确认),uplink 收到 {"ack":true,"ts":...}。
  3. 发 {"set_temp":350}:日志打印 设定值改为 350,D200 变成 350。
  4. 故意发一串非法内容(比如 hello):日志打印「下发不是合法 JSON」且程序继续跑(pcall 生效)。
  5. 停掉 Broker 再恢复:重连后再次订阅自动完成,重新下发照样能执行。

注意事项:

  • mqtt.unsubscribe 在 2026-08-16 之前的固件上会抛错(见 mqtt 库页);要兼容那批旧固件就别调用它,改用上面「在 RECVDATA 里忽略」的写法。
  • 回调在同一个线程里串行执行,里面不要做重活:收到指令只做「写寄存器 + 回 ACK」,具体动作交给梯形图。
  • 下发字段用 tonumber(...) or 现值 兜底,避免云端给个字符串把寄存器写坏。

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