Skip to content

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_1997com_dlt645.KWH_2007累计有功电能(kWh)
com_dlt645.KW_1997com_dlt645.KW_2007瞬时有功功率(kW)
com_dlt645.COS_1997com_dlt645.COS_2007功率因数
com_dlt645.V_A_1997com_dlt645.V_A_2007A 相电压
com_dlt645.V_B_1997com_dlt645.V_B_2007B 相电压
com_dlt645.V_C_1997com_dlt645.V_C_2007C 相电压
com_dlt645.A_A_1997com_dlt645.A_A_2007A 相电流
com_dlt645.A_B_1997com_dlt645.A_B_2007B 相电流
com_dlt645.A_C_1997com_dlt645.A_C_2007C 相电流
com_dlt645.KW_TIME1_1997com_dlt645.KW_TIME1_2007尖(费率 1)电能
com_dlt645.KW_TIME2_1997com_dlt645.KW_TIME2_2007峰(费率 2)电能
com_dlt645.KW_TIME3_1997com_dlt645.KW_TIME3_2007平(费率 3)电能
com_dlt645.KW_TIME4_1997com_dlt645.KW_TIME4_2007谷(费率 4)电能
com_dlt645.KW_M_TIME1_1997com_dlt645.KW_M_TIME1_2007费率 1 最大需量
com_dlt645.KW_M_TIME2_1997com_dlt645.KW_M_TIME2_2007费率 2 最大需量
com_dlt645.KW_M_TIME3_1997com_dlt645.KW_M_TIME3_2007费率 3 最大需量
com_dlt645.KW_M_TIME4_1997com_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

创建一个电表对象。

参数

#参数类型必填默认值说明
1meter_nostring是—电表号(表号),按十六进制字符解析:只取 0-9 和 A-F,其它字符(空格、横线)被忽略
2statusinteger | 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)

增加一个要读的数据项。

参数

#参数类型必填默认值说明
1data_idinteger是—DL/T 645 数据标识。用上面的常量,或直接填手册里的 DI 值
2targetinteger | 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)

设置本电表的应答等待超时。

参数

#参数类型必填默认值说明
1msinteger是—超时毫秒数

返回值

无返回值。

说明

  • 电表响应比 Modbus 仪表慢,这个值建议设到 1000ms 以上。波特率越低一帧耗时越长: 1997 版常用的 1200bps 下,一帧要 200ms 以上,窗口不要留得太紧。

dev:set_time_error ​

lua
dev:set_time_error(ms)

设置通信出错后,下一次重试前的延时。

参数

#参数类型必填默认值说明
1msinteger是—延时毫秒数

返回值

无返回值。


常见用法 ​

一条 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。

验证方法:

  1. 下载重启,D100-D101 出现总电能,D102-D103 出现功率,st:get_num() = 0(通讯成功)。 通信状态码 0=成功 / 1=报文异常 / 2=通信超时;断表后应变 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 秒一轮足够,不必过密。

验证方法:

  1. D100-D101、D110-D111 分别出现两台表电能。
  2. 拔一台表电源,对应 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 读)。

验证方法:

  1. 查电表手册确认该 DI 的真实含义与版本(1997/2007 下同一 DI 可能含义不同)。
  2. 下载后 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(),直接塞对象会得到无法序列化的结果。

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