<p style="color:#2B2B2B;font-family:-apple-system, " font-size:medium;"="">
前两篇把 DLT645 的帧结构讲透了(初识协议、07 帧结构解析),不少朋友顺着文章问:表在配电房里,电脑在办公室,中间隔着半个厂区,这数据到底怎么弄回来?
这篇就把传输这一段补上。用一块银尔达 YED-DG720Y 的 4G DTU,把 485 口上的电表数据原封不动送到公网服务器,服务器端再按 DLT645 把帧解出来。链路跑通之后,采集、存储、界面就都顺了。
整体链路长这样:
电表(RS485口) --A/B-- YED-DG720Y(4G DTU) --移动网络-- 云服务器(TCP端口) --解析程序--> 数据库/展示
先想明白:DTU 到底干了什么
配电房一般没有网线,拉 WiFi 又不现实(穿墙差、跨厂区)。而电表只有 485 口,走 DLT645 协议。DTU 做的事特别简单:把 485 线上的字节流,原样通过 4G 网络搬到服务器,反过来服务器发的数据,它也原样写回 485 线。
它不做任何解析。你发什么,它传什么。这一点很重要——意味着到了服务器端,我们面对的仍然是前面几篇讲的那些 DLT645 帧,解析逻辑一点没变,变的只是传输通道。
硬件和接线
YED-DG720Y 是银尔达的工业级单 RS485 串口 DTU,官方参数里挑几个跟项目相关的:
- 供电 5~36V 直流,推荐 12V,功率 10W。注意别用电脑 USB 供电,功率不够,模块起不来或者一加载就掉
- 4G Cat1 全网通,移动、联通、电信都能用
- 1 路 RS485,波特率 1200~230400
- 尺寸 96×26×25mm,35mm 导轨安装,外壳阻燃
- SIM 卡:内置贴片 SIM + 外置卡槽
- 协议支持 TCP、UDP、MQTT、HTTP、WebSocket,也可以直接对接阿里云、腾讯云、OneNet、华为云、涂鸦、ThingsCloud
接线就三对:
| DTU | 电表/电源 |
|---|---|
| VIN | 12V 电源正极 |
| GND | 电源负极(和电表共地更好) |
| A | 485 A(同名相颜色一致,别接反) |
| B | 485 B |
485 线用屏蔽双绞线,屏蔽层单端接地。距离几十米以内问题不大,线越长越要低速率和可靠接地。
SIM 卡缺口朝外插进卡槽,天线拧到 SMA 座上,上电后 PWR 灯亮。
串口参数先对齐
电表 485 口出厂一般默认 9600、8 数据位、无校验、1 停止位,也就是常说的 8N1。具体以电表铭牌或者说明书的通讯参数为准。
DTU 的串口参数必须跟电表完全一致,否则收上来的就是乱码,或者一帧都拼不齐。很多项目卡在第一晚调试,最后发现就是波特率或者校验位没对上,这类问题排查起来很浪费时间,建议上电之前先确认清楚。
DTU 配置:把服务器地址告诉它
用银尔达的配置工具(通过 USB 或串口连接模块)把这几项写进去:
- 工作模式选 TCP Client——我们是让 DTU 主动去连服务器
- 服务器地址填你的公网 IP 或域名
- 端口填服务器上开好的 TCP 端口
- 串口参数按上面电表实际参数填
保存后重启模块。看指示灯判断状态:
- NET 灯 100ms 快闪:卡正常但还没注册上网络,检查 SIM 卡是否欠费、天线是否接好
- NET 灯 500ms 慢闪、RDY 不亮:网络注册上了,但没连上服务器。重点查服务器地址、端口,以及服务器有没有真的在监听
- NET 灯 1000ms 慢闪、RDY 常亮:连上服务器了,可以往下走
顺带提醒一句:如果用的是银尔达的定向流量卡,服务器域名要填 dtu.yinerda.com,端口 91,别填自己服务器,这是官方 FAQ 里明确写的。
服务器端要准备什么
一台有公网 IP 的机器(云服务器最省事),做三件事:
- 开一个 TCP 端口,比如 8501
- 防火墙和安全组放行这个端口
- 跑一个 TCP server 程序,等着 DTU 连上来
这里有个新手最容易绕进去的弯:DTU 是透传,不是抄表。 电表不会自己说话,你要读数,得服务器主动往下发 DLT645 读取指令,DTU 原样转发到 485 线上,电表应答,应答帧再原路回到服务器。所以服务器程序一般按固定间隔(比如每分钟)下发一次读取指令。
服务器端解析示例(Python)
下面这段代码做四件事:监听端口、定时下发"读当前正向有功总电能"指令、收应答帧、校验并解析出电量。直接能跑,改成自己的表地址和端口就行。
# -*- coding: utf-8 -*-
"""
DLT645 电表远程采集服务端
流程: 监听TCP -> 接收DTU连接 -> 定时下发读表指令 -> 收应答帧 -> 校验 -> 解析电量
"""
import socket
import threading
import time
SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8501 # 云服务器上放行的端口
METER_ADDR = b'\x20\x17\x09\x32\x00\x72' # 你的表地址, 6字节BCD, 换成实际表号
INTERVAL = 60 # 每60秒读一次
def checksum(data):
"""DLT645校验: 从帧起始符68到数据域末字节累加, 取低8位"""
return sum(data) & 0xFF
def make_read_frame(addr):
"""
构造"读当前正向有功总电能"请求帧
请求帧: 68 地址(倒序) 68 11 04 33 33 34 33 CS 16
"""
frame = bytearray()
frame.append(0x68)
frame += addr[::-1] # 645帧里地址低字节在前, 所以倒序
frame.append(0x68)
frame.append(0x11) # 控制码: 读数据
frame.append(0x04) # 数据长度: 4字节数据标识
frame += b'\x33\x33\x34\x33' # 数据标识: 当前正向有功总电能
frame.append(checksum(frame))
frame.append(0x16)
return bytes(frame)
def parse_energy_frame(data):
"""
解析应答帧, 提取当前正向有功总电能
应答帧: 68 地址(6) 68 91 长度 数据域 CS 16
数据域 = 数据标识(4字节) + 数据(4字节BCD, 小端, 单位0.01kWh)
返回电量(kWh)或None
"""
if len(data) < 16 or data[0] != 0x68:
return None
if data[8] != 0x91: # 控制码, 91是正确应答
return None
ln = data[9] # 数据域长度
payload = data[10:10 + ln]
if checksum(data[:10 + ln]) != data[10 + ln]: # 校验不符说明帧被干扰
return None
# 电量是4字节BCD, 小端序: 比如 12 34 56 78 -> 78563412 -> 785634.12 kWh
bcd = payload[4:8][::-1]
val = 0
for b in bcd:
val = val * 100 + (b >> 4) * 10 + (b & 0x0F)
return val / 100.0
def handle_client(conn, addr):
print('[{}] DTU已连接: {}'.format(time.strftime('%H:%M:%S'), addr))
buf = b''
try:
while True:
conn.sendall(make_read_frame(METER_ADDR)) # 下发读表指令
conn.settimeout(5.0)
try:
buf += conn.recv(256)
except socket.timeout:
print(' -> 超时无应答, 查串口参数/表地址/A B是否接反')
continue
energy = parse_energy_frame(buf)
if energy is not None:
print(' -> 当前正向有功总电能: {} kWh'.format(energy))
buf = b''
time.sleep(INTERVAL)
except Exception as e:
print('连接断开: {}'.format(e))
finally:
conn.close()
def main():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((SERVER_HOST, SERVER_PORT))
srv.listen(5)
print('监听 {} 端口, 等待DTU接入...'.format(SERVER_PORT))
while True:
conn, addr = srv.accept()
threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
if __name__ == '__main__':
main()
解析的核心就是前面 DLT645 系列讲的那套:找 68 帧头、剥表地址、看控制码和长度、按数据标识取数据、校验对不上就丢帧。这里把电量(BCD、小端、0.01kWh)转成了人能看的数值。跑通了之后,把结果存数据库、做个展示页面,就是你自己发挥了。
常见坑,按顺序查
- PWR 灯都不亮:电源没接对,检查 12V 和功率
- NET 快闪:SIM 卡没插好、欠费,或者天线没拧
- 连上服务器但收不到数据:A/B 接反、串口参数不对、表地址不对(广播地址 AA AA AA AA AA AA 测试能通,不代表指定地址就行)
- 校验一直错:注意从 68 开始取帧,别把前导的 FE 也算进去
- 数据一截一截丢:485 线太长或没有终端电阻,降低波特率试试;DTU 固件 FAQ 里有 RS485 反转时间可以微调,默认 0 对应 9600 波特率下延迟约 104 微秒
- 读取间隔太短:电表应答要时间,问得太勤会被忽略,60 秒读一次已经足够能耗采集用了
链路通了之后,剩下的就是把解析结果存库、做页面、算能耗。下一篇可以讲讲一台 DTU 下面挂多块表时,怎么排队轮询不冲突。有需要的评论区说一声。
</p>
评论一下?