基于你提供的协议文档 + 实战日志,汇总 4 个核心命令码的完整定义、报文格式、交互逻辑、实战例子,覆盖所有通讯场景。
一、协议核心规则(必看)
所有交互遵循以下统一规则:
- 固定起始符 :所有报文以
0xA5开头 - 响应码规则 :响应命令码 =
0x80 + 请求命令码(如请求0x01→ 响应0x81,请求0x03→ 响应0x83) - 字节序 :所有
int32类型数据为小端字节序(低字节在前,高字节在后) - 精度规则:所有电参数(电压 / 电流 / 功率)的真实值 = 报文值 ÷ 100(精度 0.01)
- 默认模式 :电表上电默认
模式0(被动查询),仅响应0x03查询,不主动发0x04 - 通讯方向 :
- 电表 = TCP 客户端(主动发起连接)
- EMS = TCP 服务端(监听端口,被动接收连接)
二、命令码全解析(按交互顺序)
1. 命令码 0x01:连接验证
核心定义
TCP 连接建立后,电表向 EMS 发起的身份验证命令,是所有后续通讯的前置步骤,必须先完成验证才能进行数据交互。
通讯方向
电表 → EMS(客户端 → 服务端)
报文格式
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 数据(设备 SN) |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x01 |
0x10(16) |
13 字节设备 SN + 3 字节预留 |
实战请求报文(你的日志示例)
hex
A5 01 10 30 39 30 33 32 35 5A 30 30 30 30 31 00 00 00
响应报文(EMS → 电表)
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 错误码 |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x81 |
0x01(1) |
0x00= 成功,非 0 = 失败 |
实战响应报文(你的日志示例)
hex
A5 81 01 00
交互逻辑
- 电表上电,建立 TCP 连接到 EMS
- 电表发送
0x01连接验证报文(携带设备 SN) - EMS 校验 SN 合法后,回复
0x81 00(验证成功) - 电表打印「连接验证成功」,进入数据交互阶段
注意事项
- 未完成
0x01验证时,电表会忽略所有0x02/0x03命令 - 若 EMS 回复非
0x00错误码,电表会断开 TCP 并重连
2. 命令码 0x02:数据采集模式设置
核心定义
EMS 向电表发送的模式控制命令 ,用于切换电表的功率上报方式,是触发 0x04 主动上报的唯一入口。
通讯方向
EMS → 电表(服务端 → 客户端)
模式定义(协议原文)
表格
| 模式值 | 含义 |
|---|---|
0 |
默认模式:功率变化时不主动上报,仅响应 0x03 查询 |
1 |
主动上报模式:功率变化超阈值时,电表主动发 0x04 上报 |
报文格式
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 模式值 |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x02 |
0x01(1) |
0x00/0x01 |
实战请求报文
- 模式 0(默认,被动查询):
A5 02 01 00 - 模式 1(主动上报,常用):
A5 02 01 01
响应报文(电表 → EMS)
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 错误码 |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x82 |
0x01(1) |
0x00= 设置成功 |
实战响应报文
hex
A5 82 01 00
交互逻辑
- EMS 在
0x01验证成功后,发送0x02模式设置命令 - 电表收到后,切换对应模式,回复
0x82 00确认成功 - 模式立即生效,电表按新规则上报数据
注意事项
- 电表默认模式 0,不发送
0x02命令,电表永远不会发送0x04 - 模式 1 下,仍可正常发送
0x03查询,不影响主动上报逻辑 - 模式切换后,电表会保存当前模式,断电重启后默认恢复模式 0
3. 命令码 0x03:EMS 查询插座 / 电表当前功率
核心定义
EMS 主动拉取电表实时电参数的被动查询命令,是模式 0 下唯一的获取数据方式,模式 1 下仍可使用。
通讯方向
EMS → 电表(服务端 → 客户端)
报文格式
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 数据 |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x03 |
0x00(0) |
无 |
实战请求报文(你的日志示例)
hex
A5 03 00
响应报文(电表 → EMS)
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 数据段(电参数) |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x83 |
0x0C~0x1C(12~28) |
按协议定义的 int32 小端数据 |
数据段字段定义(协议原文)
表格
| 字节范围 | 字段名 | 数据类型 | 说明 |
|---|---|---|---|
| byte0-byte3 | voltage | int32 | A 相电压,精度 0.01V,真实值 = 报文值 ÷100 |
| byte4-byte7 | current | int32 | A 相电流,精度 0.01A,真实值 = 报文值 ÷100 |
| byte8-byte11 | power | int32 | 总有功功率,精度 0.01W,真实值 = 报文值 ÷100 |
| byte12-byte15 | powerReactive | int32 | 总无功功率,精度 0.01W(智能插座无此字段) |
| byte16-byte19 | powerA | int32 | A 相有功功率,精度 0.01W(智能插座无此字段) |
| byte20-byte23 | powerB | int32 | B 相有功功率,精度 0.01W(智能插座无此字段) |
| byte24-byte27 | powerC | int32 | C 相有功功率,精度 0.01W(智能插座无此字段) |
实战响应报文(你的日志示例)
hex
A5 83 1C A5 5B 00 00 B2 00 00 00 02 1F 00 00 8F 60 FF FF 02 1F 00 00 00 00 00 00 00 00 00 00
报文解析示例(按小端 + 精度规则)
表格
| 字段 | 报文字节(小端) | 原始 int32 值 | 真实值(÷100) |
|---|---|---|---|
| 电压 | A5 5B 00 00 |
0x00005BA5 = 23461 |
234.61V |
| 电流 | B2 00 00 00 |
0x000000B2 = 178 |
1.78A |
| 总有功功率 | 02 1F 00 00 |
0x00001F02 = 7938 |
79.38W |
| 总无功功率 | 8F 60 FF FF |
0xFFFF608F = -40817 |
**-408.17W**(容性负载) |
| A 相有功 | 02 1F 00 00 |
0x00001F02 = 7938 |
79.38W |
| B/C 相有功 | 00 00 00 00 |
0 |
0.00W(空载) |
交互逻辑
- EMS 定时(如 1s / 次)发送
0x03查询命令 - 电表收到后,打包当前电参数,回复
0x83响应 - EMS 解析
0x83数据段,获取实时功率数据
注意事项
- 模式 0 下,必须通过
0x03才能获取数据,电表不会主动上报 - 智能插座场景:数据长度为 12 字节(仅电压 / 电流 / 总有功),无后续无功 / 分相字段
- 三相电表场景:数据长度为 28 字节(7 个 int32),包含分相功率
4. 命令码 0x04:插座 / 电表主动上报功率
核心定义
电表在模式 1 下,功率变化超阈值时,主动向 EMS 上报的功率数据命令,无需 EMS 查询,实现实时推送。
通讯方向
电表 → EMS(客户端 → 服务端)
触发条件
- EMS 通过
0x02命令将电表设置为模式 1 - 电表检测到当前功率变化超过预设阈值(如 ±10W)
报文格式
表格
| 字段 | 起始符 | 命令码 | 数据长度 | 数据段(电参数) |
|---|---|---|---|---|
| 取值 | 0xA5 |
0x04 |
0x0C~0x1C(12~28) |
与 0x83 响应数据段完全一致 |
实战报文示例
hex
A5 04 1C A5 5B 00 00 B2 00 00 00 02 1F 00 00 8F 60 FF FF 02 1F 00 00 00 00 00 00 00 00 00 00
交互逻辑
- EMS 发送
0x02 01设置模式 1,电表回复0x82 00 - 电表实时监测功率,当变化超阈值时,主动发送
0x04报文 - EMS 接收
0x04报文,按0x83的解析逻辑提取数据,无需回复响应
注意事项
- **仅模式 1 下会触发
0x04**,默认模式 0 下电表不会发送 0x04的数据段格式、解析规则与0x83完全一致,可复用同一套解析代码- 主动上报阈值可在电表端配置,默认通常为 ±5~10W
三、完整通讯流程(两种模式)
1. 模式 0(默认,被动查询)完整流程
plaintext
1. TCP连接建立
2. 电表 → EMS:发送0x01连接验证(带SN)
3. EMS → 电表:回复0x81 00(验证成功)
4. EMS → 电表:定时发送0x03查询命令
5. 电表 → EMS:回复0x83带功率数据
6. 循环步骤4-5,直到TCP连接断开
2. 模式 1(主动上报)完整流程
plaintext
1. TCP连接建立
2. 电表 → EMS:发送0x01连接验证(带SN)
3. EMS → 电表:回复0x81 00(验证成功)
4. EMS → 电表:发送0x02 01(设置模式1,主动上报)
5. 电表 → EMS:回复0x82 00(设置成功)
6. 电表功率变化超阈值 → 主动发送0x04带数据
7. EMS接收0x04数据,同时仍可发送0x03查询
8. 直到TCP连接断开
四、常见问题汇总(对应你的测试场景)
- 为什么日志里只有 0x03 命令?
- 电表默认模式 0,仅响应
0x03查询;你未发送0x02命令,所以不会出现0x02/0x04 - 解决:在 EMS 端发送
0x02 01切换模式 1,即可触发0x02/0x04交互
- 电表默认模式 0,仅响应
- 电表报 errno 128(网络不可达)?
- 电表仍在连接你的笔记本内网 IP(如 192.168.12.27),跨网场景下无法路由
- 解决:修改电表连接目标为 frp 内网穿透的公网 IP + 映射端口
- 0x04 主动上报不出现?
- 未通过
0x02设置模式 1,或功率变化未超过阈值 - 解决:发送
0x02 01,并给电表加负载(如插电器)触发功率变化
- 未通过
- 报文解析数值不对?
- 未按小端字节序解析,或未除以 100
- 解决:严格按「小端重组 → 转十进制 →÷100」的规则解析
五、实战测试用例(一键跑通全命令)
测试目标:跑通 4 个命令码,验证完整协议
步骤 1:启动模拟 EMS 服务(Python)
python
运行
import socket
HOST = '0.0.0.0'
PORT = 10241
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print(f"✅ 模拟EMS服务启动,监听{HOST}:{PORT}")
conn, addr = s.accept()
with conn:
print(f"🔌 电表已连接:{addr}")
# 1. 处理0x01连接验证
data = conn.recv(1024)
print(f"📥 收到0x01验证报文:{data.hex()}")
# 回复0x81 00(验证成功)
conn.send(bytes.fromhex("A5 81 01 00"))
print("📤 发送0x81 00:连接验证成功")
# 2. 发送0x02 01,设置模式1(主动上报)
mode_cmd = bytes.fromhex("A5 02 01 01")
conn.send(mode_cmd)
print("📤 发送0x02 01:设置主动上报模式")
# 接收0x82响应
data = conn.recv(1024)
print(f"📥 收到0x82响应:{data.hex()}")
# 3. 循环处理0x03查询/0x04上报
while True:
data = conn.recv(1024)
if not data:
break
print(f"📥 收到报文:{data.hex()}")
# 若收到0x03查询,回复0x83(模拟电表)
if data[1] == 0x03:
resp = bytes.fromhex("A5 83 1C A5 5B 00 00 B2 00 00 00 02 1F 00 00 8F 60 FF FF 02 1F 00 00 00 00 00 00 00 00 00 00")
conn.send(resp)
print("📤 发送0x83:功率数据响应")
步骤 2:启动内网穿透(svnproxy)
cmd
cd C:\Users\Acrel\Desktop\ems_visitor
svnproxy.exe -c frpc_ss_ems.ini
步骤 3:修改电表连接目标为穿透公网 IP,上电测试
- 电表日志会依次出现:
0x01→0x81→0x02→0x82→0x03→0x83/0x04,4 个命令码全部跑通