主题
com_dlt645 — DLT645 电表
模块名:com_dlt645
按 DL/T 645 标准读取电表数据,同时支持 1997 和 2007 两个版本的数据标识。和其它串口协议模块一样,本模块只描述「要读这台表的哪些量」,必须用 com.add_device 挂到一个 com 对象上,由 com.start 的轮询线程驱动。
DLT645 是只读协议,本模块不提供写电表的能力。
除 creat 之外的函数在库表和对象元表里都注册了,com_dlt645.add_channel(dev, ...) 与 dev:add_channel(...) 等价。
函数总览
库级函数
| 函数 | 作用 |
|---|---|
com_dlt645.creat(meter_number, [data_state]) | 创建一个电表对象 |
对象方法
| 方法 | 作用 |
|---|---|
dev:add_channel | 增加一个要读的数据项 |
dev:set_time_recv | 设置应答等待超时 |
dev:set_time_error | 设置出错后的重试延时 |
常量:数据标识
每个常量对应一个 DL/T 645 数据标识。_1997 后缀是 1997 版,_2007 后缀是 2007 版,两版标识不通用,必须按电表实际支持的版本选。
| 1997 版 | 2007 版 | 含义 |
|---|---|---|
com_dlt645.KWH_1997 | com_dlt645.KWH_2007 | 累计有功电能(kWh) |
com_dlt645.KW_1997 | com_dlt645.KW_2007 | 瞬时有功功率(kW) |
com_dlt645.COS_1997 | com_dlt645.COS_2007 | 功率因数 |
com_dlt645.V_A_1997 | com_dlt645.V_A_2007 | A 相电压 |
com_dlt645.V_B_1997 | com_dlt645.V_B_2007 | B 相电压 |
com_dlt645.V_C_1997 | com_dlt645.V_C_2007 | C 相电压 |
com_dlt645.A_A_1997 | com_dlt645.A_A_2007 | A 相电流 |
com_dlt645.A_B_1997 | com_dlt645.A_B_2007 | B 相电流 |
com_dlt645.A_C_1997 | com_dlt645.A_C_2007 | C 相电流 |
com_dlt645.KW_TIME1_1997 | com_dlt645.KW_TIME1_2007 | 尖(费率 1)电能 |
com_dlt645.KW_TIME2_1997 | com_dlt645.KW_TIME2_2007 | 峰(费率 2)电能 |
com_dlt645.KW_TIME3_1997 | com_dlt645.KW_TIME3_2007 | 平(费率 3)电能 |
com_dlt645.KW_TIME4_1997 | com_dlt645.KW_TIME4_2007 | 谷(费率 4)电能 |
com_dlt645.KW_M_TIME1_1997 | com_dlt645.KW_M_TIME1_2007 | 费率 1 最大需量 |
com_dlt645.KW_M_TIME2_1997 | com_dlt645.KW_M_TIME2_2007 | 费率 2 最大需量 |
com_dlt645.KW_M_TIME3_1997 | com_dlt645.KW_M_TIME3_2007 | 费率 3 最大需量 |
com_dlt645.KW_M_TIME4_1997 | com_dlt645.KW_M_TIME4_2007 | 费率 4 最大需量 |
com_dlt645.KW_MAX_1997 | — | 最大需量(仅 1997 版注册) |
除了这些预置常量,add_channel 的第一个参数也接受任意整数,可以直接填电表手册上的数据标识(DI)值,用来读常量表里没有的量。
com_dlt645.creat
lua
com_dlt645.creat(meter_no, [status]) --> device创建一个电表对象。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | meter_no | string | 是 | — | 电表号(表号),按十六进制字符解析:只取 0-9 和 A-F,其它字符(空格、横线)被忽略 |
| 2 | status | integer | data | 否 | — | 通信状态回写位置。数字 = D 寄存器编号,data 对象 = 写到该对象 |
返回值
| 类型 | 说明 |
|---|---|
| userdata | 电表对象。必须用变量接住 |
示例
lua
local st = data.creat(0)
-- 表号 12 位,可以带分隔符
local m1 = com_dlt645.creat("000012345678", st)
local m2 = com_dlt645.creat("0000-1234-5678", st) -- 等价,横线被忽略说明
- 表号按字符逐位转十六进制,所以
"123456789012"得到的是0x123456789012。抄电表铭牌上的数字直接填即可。 - 小写字母
a-f不会被识别,十六进制表号请一律用大写A-F。
实用例程
lua
--开启mc协议服务器,端口为5000
mcs=mc_server.start(5000,4,1024)
--读取电表
state=data.creat(0)
dlt=com_dlt645.creat('<设备ID或凭据>',state)--建立电表驱动参数为表号12位表号2007广播表号
kwh=data.creat(0)
kw=data.creat(0)
dlt:add_channel(com_dlt645.KW_2007,kw);--读取功率
dlt:add_channel(com_dlt645.KWH_2007,kwh);--读取正向有功总电量到D0
com1=com.setup(1,2400,com.EVEN,8,1)--打开串口父设备2007表默认2400BPS
com1:add_device(dlt)--在串口父设备中加入电表子设备
com1:start()--启动串口父设备
local 倍率=1
dssc=timer.start(5000,function()--启动定时器 5 秒发布一个数据
local kwhreal=kwh:get_num()*倍率
--local kwhreal = string.format("%.2f",kwh:get_num()*倍率)
local jsontable =
{
name="<名称>",
kw=kw:get_num()*倍率,
kwh = kwhreal,
}
plc.df(200,kwhreal)--把电能浮点值保存到D200
local jsonstring = json.encode(jsontable)
--print(jsonstring)
print("<名称>",plc.df(200),"<名称>",plc.dd(204))
end
)把上面的地址、主题、引脚替换成你项目里的即可。
dev:add_channel
lua
dev:add_channel(data_id, target)增加一个要读的数据项。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | data_id | integer | 是 | — | DL/T 645 数据标识。用上面的常量,或直接填手册里的 DI 值 |
| 2 | target | integer | data | 是 | — | 值的落点。数字 = D 寄存器编号;data 对象 = 写到该对象(本协议读的都是电能/功率这类字类型量,所以数字落点就是 D 区) |
返回值
无有效返回值。
示例
lua
m1:add_channel(com_dlt645.KWH_2007, 100) -- 总电能 → D100
m1:add_channel(com_dlt645.KW_2007, 102) -- 瞬时功率 → D102
m1:add_channel(com_dlt645.V_A_2007, kwh) -- A 相电压 → data 对象说明
- 电能、功率这类量是带小数的浮点值,落到 D 寄存器时按浮点存放,读的时候用
plc.df而不是plc.d。 - 数据标识选错版本(1997 用在 2007 表上)会读不到值,
status会反映出通信异常。
dev:set_time_recv
lua
dev:set_time_recv(ms)设置本电表的应答等待超时。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ms | integer | 是 | — | 超时毫秒数 |
返回值
无返回值。
说明
- 电表响应比 Modbus 仪表慢,这个值建议设到 1000ms 以上。波特率越低一帧耗时越长: 1997 版常用的 1200bps 下,一帧要 200ms 以上,窗口不要留得太紧。
dev:set_time_error
lua
dev:set_time_error(ms)设置通信出错后,下一次重试前的延时。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ms | integer | 是 | — | 延时毫秒数 |
返回值
无返回值。
常见用法
一条 RS485 抄两台 2007 版电表
lua
local st1, st2 = data.creat(0), data.creat(0)
local m1 = com_dlt645.creat("000012345678", st1)
m1:set_time_recv(1500)
m1:set_time_error(5000)
m1:add_channel(com_dlt645.KWH_2007, 100) -- 总电能 → D100-D101
m1:add_channel(com_dlt645.KW_2007, 102) -- 有功功率 → D102-D103
m1:add_channel(com_dlt645.V_A_2007, 104) -- A 相电压 → D104-D105
local m2 = com_dlt645.creat("000012345679", st2)
m2:set_time_recv(1500)
m2:add_channel(com_dlt645.KWH_2007, 110)
-- 电表口标准参数:2400,偶校验,8 数据位,1 停止位
local port = com.setup(1, 2400, com.EVEN, 8, 1)
port:add_device(m1)
port:add_device(m2)
port:set_time_lunxun(5000) -- 电表变化慢,5 秒一轮足够
port:start()
timer.start(10000, function()
print(string.format("1# 电能 %.2f kWh 功率 %.3f kW 通讯状态 %d",
plc.df(100), plc.df(102), st1:get_num())) -- 0=成功 1=报文异常 2=超时
end)注意事项
- 串口参数按规约版本走:DL/T 645-1997 版出厂默认
1200, 8, E, 1, DL/T 645-2007 版默认2400, 8, E, 1。本页例程抄的是 2007 版,所以填 2400; 现场是 1997 版的老表就把波特率改成1200(完整写法见 综合例程:DLT645 电表采集上云)。 参数错的表现是一直通信异常(status非 0),而不是脚本报错。 - 1997 / 2007 标识不通用,应先确认电表版本再选择常量。读不到数据时应优先排查此项。
- 电表响应较慢,
set_time_recv应设置足够长(≥1000ms),set_time_lunxun也不必过密(电能量变化慢,数秒一轮即可)。 - 表号只识别数字和大写
A-F。 - 本协议只读,不能写电表参数。
- 电能、功率是浮点值,从 D 区取值用
plc.df。 - 常量表里没有的量,直接把手册上的数据标识当整数传给
add_channel。
例程
每个例程都是可直接下载运行的完整脚本:
com_dlt645只描述「要读这台表的哪些量」,挂到com并start后由后台线程轮询,脚本注册完即结束。 本协议只读,不能写电表。统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。
例程 1:抄一台 2007 版电表(电能 / 功率 / 电压)
适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 | 依赖模块:com_dlt645 com data timer plc | 难度:★☆☆
场景:现场一台 2007 版三相电表,要把总电能、瞬时有功功率、A 相电压自动读进 D 区,定时打印。
完整代码(可直接下载运行):
lua
local st = data.creat(0)
local m1 = com_dlt645.creat("000012345678", st) -- 表号 12 位,按铭牌抄
m1:set_time_recv(1500) -- 电表慢,超时设大
m1:set_time_error(5000)
m1:add_channel(com_dlt645.KWH_2007, 100) -- 总电能 → D100-D101
m1:add_channel(com_dlt645.KW_2007, 102) -- 有功功率 → D102-D103
m1:add_channel(com_dlt645.V_A_2007, 104) -- A 相电压 → D104-D105
local port = com.setup(1, 2400, com.EVEN, 8, 1) -- 645 标准 2400-8-E-1
port:add_device(m1)
port:set_time_lunxun(5000)
port:start()
timer.start(10000, function()
print(string.format("电能 %.2f kWh 功率 %.3f kW 通讯状态 %d",
plc.df(100), plc.df(102), st:get_num())) -- 0=成功 1=报文异常 2=超时
end)要点:
- 表号按铭牌 12 位抄,大写
A-F;可带横线(自动忽略)。 - 串口参数按 DL/T 645-2007 默认:2400 / 8 数据位 / 偶校验 / 1 停止位(1997 版的老表用 1200)。
- 电能、功率是浮点,落 D 区后用
plc.df读,不是plc.d。
验证方法:
- 下载重启,D100-D101 出现总电能,D102-D103 出现功率,
st:get_num()= 0(通讯成功)。 通信状态码0=成功 / 1=报文异常 / 2=通信超时;断表后应变 2。 - 用厂家软件读同一块表比对数值。
注意事项:
- 1997 / 2007 标识不通用:表是 2007 版就必须用
KWH_2007,用_1997会读不到,status反映通信异常。 - 表号小写
a-f不被识别,一律大写。 - 电表响应慢,
set_time_recv设 ≥1000ms,否则频繁超时。
例程 2:一条 RS485 抄两台 2007 版电表
适用:同上 | 依赖模块:com_dlt645 com data timer plc | 难度:★☆☆
场景:同一总线挂两台电表,分别抄总电能到不同 D 区。
完整代码(可直接下载运行):
lua
local st1, st2 = data.creat(0), data.creat(0)
local m1 = com_dlt645.creat("000012345678", st1)
m1:set_time_recv(1500)
m1:add_channel(com_dlt645.KWH_2007, 100) -- 1# 总电能 → D100-D101
local m2 = com_dlt645.creat("000012345679", st2)
m2:set_time_recv(1500)
m2:add_channel(com_dlt645.KWH_2007, 110) -- 2# 总电能 → D110-D111
local port = com.setup(1, 2400, com.EVEN, 8, 1)
port:add_device(m1)
port:add_device(m2)
port:set_time_lunxun(5000)
port:start()
timer.start(10000, function()
print("1# 电能", plc.df(100), "2# 电能", plc.df(110))
end)要点:
- 两台表表号不同,各自
creat独立对象,挂在同一个com上由轮询线程依次访问。 - 电能量变化慢,5 秒一轮足够,不必过密。
验证方法:
- D100-D101、D110-D111 分别出现两台表电能。
- 拔一台表电源,对应
st变 0,不影响另一台。
注意事项:
- 表号抄错会把邻居家电表的数据读进来,现场核对 12 位表号。
- 波特率要和表的规约版本对上:2007 版用 2400,1997 版常用 1200;用错会完全读不到。
例程 3:读常量表没有的量(直接传数据标识 DI 整数)
适用:同上 | 依赖模块:com_dlt645 com data plc | 难度:★★☆
场景:想读「电压谐波」之类常量表里没有的量,用手册上的数据标识(DI)直接传给 add_channel。
完整代码(可直接下载运行):
lua
-- 读常量表未覆盖的量:直接用手册上的 DI 值(示例值,按你的电表手册填)
local st = data.creat(0)
local m = com_dlt645.creat("000012345678", st)
m:add_channel(0x0201FF00, 120) -- 直接传 DI 整数(十六进制字面量或十进制均可)
local port = com.setup(1, 2400, com.EVEN, 8, 1)
port:add_device(m)
port:set_time_lunxun(5000)
port:start()
timer.start(10000, function()
print("自定义量:", plc.df(120), "在线:", st:get_num())
end)要点:
add_channel第一参除了预置常量,也接受任意整数 DI,用来读常量表没覆盖的量。- 落在 D120(浮点,用
plc.df读)。
验证方法:
- 查电表手册确认该 DI 的真实含义与版本(1997/2007 下同一 DI 可能含义不同)。
- 下载后 D120 出现对应数值。
注意事项:
- 不同版本下同一 DI 含义可能不同,确认版本再填。
- 写错 DI 读到的数无意义(不会报错)。
- 仍要遵守 2400-8-E-1 串口参数与表号大写规则。
例程 4:现场常用脚本 电表.LPLUA(1997 版抄表 + MQTT 上云,原文)
适用:同上 | 依赖模块:com_dlt645 com data mqtt json timer | 难度:★★☆
场景:抄一块 1997 版电表的正向有功总电量与功率,每 2 秒打包成 JSON 用 MQTT 发出去。 这是电表项目里用得最多的一份脚本(文件 电表.LPLUA),原文收录如下。
完整代码(电表.LPLUA 原文):
lua
dlt=com_dlt645.creat("999999999999")--建立电表驱动参数为表号12位表号
kwh=data.creat(0)
kw=data.creat(0)
dlt:add_channel(com_dlt645.KWH_1997,kwh);--读取正向有功总电量到D0
dlt:add_channel(com_dlt645.KW_1997,kw);--读取功率
com1=com.setup(2,1200,com.EVEN,8,1)--打开串口父设备
com1:add_device(dlt)--在串口父设备中加入电表子设备
com1:start()--启动串口父设备
--数据上传
x=mqtt.creat("119.45.204.172",1883,"测试电表1","guest","guest")--建立 MQTT 对象
mqtt.set_keepalive(x,30)
x:start()--启动 MQTT 协议栈
dssc=timer.start(2000,function()--启动定时器 2 秒发布一个数据
local jsontable =
{
name="电表名",
kw=kw:get_num(),
kwh = kwh:get_num(),
}
local jsonstring = json.encode(jsontable)
if mqtt.is_linked(x) then
mqtt.publish(x,"watthour meter",jsonstring,0)--支持 QOS0,QOS1,QOS2
end
end
)要点:
- 串口参数
1200, com.EVEN, 8, 1是 1997 版的默认格式(2007 版是2400,8,E,1),常量和串口参数都要 按电表实际版本配套,混用必然读不到数。 - 原脚本注释写「读取正向有功总电量到D0」,但
add_channel第 2 个参数传的是data对象(kwh), 并不落在 D0,取值得用kwh:get_num()。这句注释是早期版本留下的,别按它去plc.d(0)找数。 - 表号全
9是广播地址,只能读不能定位到具体表(上线会失败),现场要换成电表实际的 12 位表号。 if mqtt.is_linked(x) then这句有缺陷:is_linked返回的是整数0/1,而 Lua 里0也算真, 所以断线时仍会执行publish。要写成if mqtt.is_linked(x) == 1 then。
建议修正后的发布部分:
lua
dssc = timer.start(2000, function()
local jsontable = { name = "电表名", kw = kw:get_num(), kwh = kwh:get_num() }
if mqtt.is_linked(x) == 1 then -- 必须显式比较,0 在 Lua 里为真
mqtt.publish(x, "watthour meter", json.encode(jsontable), 0)
else
print("MQTT 未连接,本次不发布")
end
end)验证方法:下载后日志里 kwh/kw 有值,用 MQTT 客户端订阅 watthour meter 能收到 JSON; 把 MQTT 服务器断开,修正后的脚本会打印「未连接」而不是静默丢包。
注意事项:mqtt.creat 的五个参数(IP、端口、客户端 ID、用户名、密码)缺一不可,客户端 ID 多台设备要 不重复(重复会互顶下线);json.encode 对 data 对象要写 kw:get_num(),直接塞对象会得到无法序列化的结果。