Skip to content

综合例程:DL/T 645 电表采集 + MQTT/JSON 上报 ​

电表是现场最常见的第三方设备:抄总电量、抄功率、抄电压电流,然后定时上报云端。 这条链路把三个模块串起来:com_dlt645 读表 → json 打包 → mqtt 发布。 本页特别纠正一个几乎人人都写错的判断:MQTT 在线判断(见第五节)。


一、场景与点位分配 ​

一台三相电表挂在 COM2 上,需要每 2 秒把电能、功率发到云端主题,同时在本机留一份可读值。

位置地址说明
电表通信规约DL/T 645 1997 版2007 版要用另一套常量
电表表号铭牌上的 12 位广播抄表才用 999999999999(1997)/ AAAAAAAAAAAA(2007)
串口 21200, 8, 偶校验, 11997 版默认速率,不是 2400
累计有功电能kwh(data 对象)com_dlt645.KWH_1997
瞬时有功功率kw(data 对象)com_dlt645.KW_1997
通信状态st(data 对象)0=成功,非 0=异常/超时
MQTT 上报主题watthour meter按现场要求改

二、完整代码(可直接下载运行) ​

lua
-- ============================================================
-- DL/T 645 抄表 -> JSON 打包 -> MQTT 定时上报
-- 全程事件驱动,脚本注册完就结束,采集由后台轮询线程做
-- ============================================================

-- ---------- 1. 电表子设备 ----------
local st  = data.creat(0)                                       -- 通信状态对象
local dlt = com_dlt645.creat("999999999999", st)                 -- 12 位表号 + 状态回写
local kwh = data.creat(0)
local kw  = data.creat(0)
dlt:add_channel(com_dlt645.KWH_1997, kwh)                        -- 累计有功电能
dlt:add_channel(com_dlt645.KW_1997,  kw)                         -- 瞬时有功功率
dlt:set_time_recv(1000)                                          -- 表子慢,应答窗口给够
dlt:set_time_error(2000)                                         -- 出错后 2 秒再试

-- ---------- 2. 串口父设备 ----------
local port = com.setup(2, 1200, com.EVEN, 8, 1)                  -- 1997 版:1200 / 偶校验
if not port then
    print("串口打开失败,检查端口号与波特率")
    return
end
port:add_device(dlt)
port:set_time_lunxun(3000)                                       -- 电能量变化慢,3 秒一轮足够
port:start()

-- ---------- 3. MQTT ----------
local BROKER   = "119.45.204.172"
local TOPIC    = "watthour meter"
local METER_NAME = "电表名"
local RATIO    = 1                                               -- 互感器变比,直接表读数填 1

x = mqtt.creat(BROKER, 1883, "测试电表1", "guest", "guest")        -- 必须长期持有,用全局
mqtt.set_keepalive(x, 30)                                         -- 必须在 start 之前
mqtt.reg(x, mqtt.CONNECTED, function()
    print("MQTT 已连接")
end)
mqtt.reg(x, mqtt.DISCONNECTED, function()
    print("MQTT 断开,内部会自动重连")
end)
x:start()

-- ---------- 4. 定时上报 ----------
timer.start(2000, function()
    if st:get_num() ~= 0 then
        print("电表通信异常,状态=", st:get_num())                -- 只报异常,不刷屏
        return
    end
    local payload = json.encode({
        name = METER_NAME,
        kwh  = kwh:get_num() * RATIO,
        kw   = kw:get_num()  * RATIO,
    })
    if mqtt.is_linked(x) == 1 then                                -- 必须 == 1,见第五节
        mqtt.publish(x, TOPIC, payload, 0)                        -- QoS 0
        print("已上报:", payload)
    end
end)

三、逐段要点 ​

  1. 表号不要留广播值上线。999999999999 是 1997 版的广播表号, 一条总线上挂多台表时会同时应答、数据冲突。正式部署必须抄铭牌填 12 位真实表号。
  2. 表号只识别数字和大写 A-F,空格横线会被忽略,小写字母不会被认。
  3. status 对象建议传:不传就读不到数据时无法区分"表停电了"和"电量确实是 0"。
  4. 串口参数按规约版本走:1997 版常用 1200, 8, E, 1,2007 版常用 2400, 8, E, 1。 参数错的表现是"一直通信异常",不是报错。
  5. 1997 / 2007 常量不通用:给 2007 表用 KWH_1997 读不到,读不到时先查这一项。
  6. 电表响应慢:set_time_recv 至少 1000ms;set_time_lunxun 数秒一轮就够, 电能不是需要毫秒级刷新的量,轮询过密只是占总线。
  7. 变比要自己乘:经互感器接入时表读数不是实际电量,RATIO 按现场变比填(如 120)。 代码里集中成一个常量,别散落各处。
  8. mqtt.set_keepalive 必须在 start 之前,启动后再设不生效。
  9. MQTT 对象用全局变量长期持有:x 若写成函数里的局部变量, 函数返回后对象被回收,表现是"连上了一会儿自己断了"。
  10. 上报里先判通信状态再取数:异常时通道值是上一次的旧值,直接发出去云端就存了一条假数据。

四、本机侧怎么取这些值 ​

通道绑到 data 对象时,脚本和梯形图都用对象;绑到 D 区时(add_channel 的第 2 参填数字):

绑法写法本机怎么读
data 对象dlt:add_channel(com_dlt645.KWH_1997, kwh)kwh:get_num()
D 区dlt:add_channel(com_dlt645.KWH_1997, 200)plc.df(200)(电能/功率是浮点值)

电能量、功率这类量是浮点,从 D 区取要用 plc.df,用 plc.d 取到的是错的整数。 要上云又想让梯形图参与判断时,建议绑 D 区 —— 梯形图和云端画面都能直接取, data 对象只有脚本能用。


五、错误写法 vs 正确写法对照 ​

lua
-- 错误:把 is_linked 当布尔用
if mqtt.is_linked(x) then
    mqtt.publish(x, TOPIC, payload, 0)
end
问题后果正确做法
mqtt.is_linked() 返回整数 1/0,而 Lua 里 0 为真条件恒成立,断线时照样往下发布;数据静默丢失,日志看起来"一切正常"if mqtt.is_linked(x) == 1 then
广播表号 999999999999 直接上线多表冲突,数值时有时无填铭牌真实 12 位表号
1997 表配 2400 波特率永远通信异常,且不报错1997 用 1200,2007 用 2400
用 KWH_2007 读 1997 表读不到数据先确认表的规约版本再选常量
通信异常时照常上报云端存进一条过期旧值,看板数据不可信先判 st:get_num() == 0 再发
set_keepalive 写在 start 之后保持仍是默认值,NAT/4G 链路下会被悄悄掐断建对象后、start 前设

同一处错误在客户脚本里出现的比例非常高,因为它不报任何错。 凡是拿到整数当条件用的地方(is_linked、status、各模块返回 1/0 的函数), 一律写明确的 == 1 或 == 0,别省略。


六、注意事项汇总 ​

  • com_dlt645 只读,不能写电表参数(拉闸、设费率都不行)。
  • add_device 只认 com_modbus / com_fx / com_dlt645 三种对象, 传错类型静默忽略 —— 抄不到数先回来查这一行。
  • 通道要在 port:start() 之前加完。
  • 常量表里没有的量,可以把电表手册上的数据标识当整数直接传给 add_channel。
  • 一条 485 上多台表:给每台表各建一个 com_dlt645 对象、各自 add_device 到同一个串口, 轮询线程会依次访问;表多时把 set_time_lunxun 放大,别把总线塞满。
  • MQTT 断线期间 publish 不报错:本模块没有缓存补传,断线时段的数据就是丢了。 要补传得自己在线时回读、或改走云平台的采集通道。
  • 需要按需读一次、并拿到每次通信的错误码时,用 pipe 的 dlt645 驱动, 不是本模块。

七、相关文档 ​

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