主题
综合例程:DL/T 645 电表采集 + MQTT/JSON 上报
电表是现场最常见的第三方设备:抄总电量、抄功率、抄电压电流,然后定时上报云端。 这条链路把三个模块串起来:
com_dlt645读表 →json打包 →mqtt发布。 本页特别纠正一个几乎人人都写错的判断:MQTT 在线判断(见第五节)。
一、场景与点位分配
一台三相电表挂在 COM2 上,需要每 2 秒把电能、功率发到云端主题,同时在本机留一份可读值。
| 位置 | 地址 | 说明 |
|---|---|---|
| 电表通信规约 | DL/T 645 1997 版 | 2007 版要用另一套常量 |
| 电表表号 | 铭牌上的 12 位 | 广播抄表才用 999999999999(1997)/ AAAAAAAAAAAA(2007) |
| 串口 2 | 1200, 8, 偶校验, 1 | 1997 版默认速率,不是 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)三、逐段要点
- 表号不要留广播值上线。
999999999999是 1997 版的广播表号, 一条总线上挂多台表时会同时应答、数据冲突。正式部署必须抄铭牌填 12 位真实表号。 - 表号只识别数字和大写
A-F,空格横线会被忽略,小写字母不会被认。 status对象建议传:不传就读不到数据时无法区分"表停电了"和"电量确实是 0"。- 串口参数按规约版本走:1997 版常用
1200, 8, E, 1,2007 版常用2400, 8, E, 1。 参数错的表现是"一直通信异常",不是报错。 - 1997 / 2007 常量不通用:给 2007 表用
KWH_1997读不到,读不到时先查这一项。 - 电表响应慢:
set_time_recv至少 1000ms;set_time_lunxun数秒一轮就够, 电能不是需要毫秒级刷新的量,轮询过密只是占总线。 - 变比要自己乘:经互感器接入时表读数不是实际电量,
RATIO按现场变比填(如 120)。 代码里集中成一个常量,别散落各处。 mqtt.set_keepalive必须在start之前,启动后再设不生效。- MQTT 对象用全局变量长期持有:
x若写成函数里的局部变量, 函数返回后对象被回收,表现是"连上了一会儿自己断了"。 - 上报里先判通信状态再取数:异常时通道值是上一次的旧值,直接发出去云端就存了一条假数据。
四、本机侧怎么取这些值
通道绑到 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 驱动, 不是本模块。
七、相关文档
- com_dlt645 — DL/T 645 电表:常量表与全部方法
- mqtt — MQTT 客户端:
is_linked/set_keepalive/ 回调 - json — JSON:
encode/decode - data — 共享变量:
creat/get_num/set - plc — PLC 数据交互:
plc.df取浮点 - 综合例程:采集第三方设备
- 综合例程:Modbus 采集 + MQTT 上报
- 对接 MQTT 收发 JSON