- Add docs/references/SDM120-Modbus-Protocol.md extracted from the SDM120 PDF - M4 login hardening: brute-force exponential backoff, CLI escape hatch (reset-password/unlock/disable-totp), optional TOTP 2FA - M5 IoT/energy: Modbus-TCP SDM120 polling + energy tables, MQTT/HA Discovery expose framework, frontend sidebar + energy view - Add manual acceptance walkthroughs (M4 lock/unlock; M5 energy_cli read) - Index both milestones in docs/design/README.md
7.8 KiB
SDM120 Modbus 协议(从官方 PDF 提取)
来源:
docs/references/SDM120-MODBUS_Protocol.pdf(Eastron SDM120 Modbus Smart Meter Modbus Protocol Implementation V2.4) 本文件是 PDF 的可读化提取,供本项目的 Modbus 采集驱动设计参考。以官方 PDF 为准,本文件如有出入以 PDF 为准。
0. 本项目的接入方式(重要)
SDM120 物理层是 Modbus RTU(RS-485 串口)。本项目通过一个 Modbus-TCP 网关接入:
- 后端用 Modbus TCP(
IP:port)连到网关,网关在串口侧转成 RTU 与电表通信。 - TCP 帧用 MBAP header、无 CRC(CRC/Error Check 由网关在 RTU 侧处理)。本文档里 RTU 帧的
Error Check (Lo/Hi)字段在 TCP 模式下不需要我们关心。 - Slave Address / Unit ID = 电表的 Meter ID(默认
1,范围 1–247),在 TCP 请求里作为 unit id 传入。 - 若以后直连串口(RTU),才需要管波特率 / 校验位 / CRC(见 §5 holding 寄存器)。
1. 协议帧格式
MODBUS 定义 master 查询 / slave 响应的格式。Eastron 电表用 16-bit 寄存器在主从间传值,但实际数据是 32-bit IEEE-754 浮点,因此每个测量参数占两个相邻的 16-bit 寄存器。
Query(master → slave,RTU 帧)
| 字段 | 说明 |
|---|---|
| Slave Address | 8-bit,目标从机地址 1–247(0=广播,Eastron 不支持广播) |
| Function Code | 8-bit,功能码(Eastron 支持 03 / 04 / 08 / 16(=0x10)) |
| Start Address (Hi/Lo) | 16-bit 起始寄存器地址;寄存器成对使用、从 0 开始,所以起始地址必须是偶数 |
| Number of Points (Hi/Lo) | 16-bit 请求的寄存器数量;也必须是偶数(成对读浮点) |
| Error Check (Lo/Hi) | 16-bit CRC(RTU 模式;TCP 网关模式无此字段) |
Response(slave → master)
| 字段 | 说明 |
|---|---|
| Slave Address | 响应从机地址 |
| Function Code | 与查询相同的功能码(表示识别并已响应) |
| Byte Count | 8-bit,本次返回的数据字节数 |
| Data (寄存器对) | 每个寄存器 Hi byte / Lo byte,按"高寄存器在前"排列 |
| Error Check (Lo/Hi) | 16-bit CRC(RTU 模式) |
Exception Response(异常响应)
- 异常响应的 Function Code = 查询功能码 OR 0x80(最高位置 1)。
- 数据是单字节 Error Code(异常码)。
- PDF 正文写了"见后文 Table Of Exception Codes",但提取的 8 页里没有附上该表。标准 Modbus 异常码(供参考,非本 PDF 内容):
01非法功能、02非法数据地址、03非法数据值、04从机设备故障。
2. 功能码
| 功能码 | 作用 | 寄存器区 |
|---|---|---|
| 04 | Read Input Registers(读输入寄存器,3X)—— 所有测量值都在这里 | 30001+ |
| 03 | Read Holding Registers(读保持寄存器,4X)—— 配置项 | 40001+ |
| 16 / 0x10 | Write Holding Registers(写保持寄存器,4X)—— 改配置 | 40001+ |
| 08 | Diagnostics(诊断) | — |
⚠️ 测量值用 FC 04(输入寄存器),不是 FC 03。 这是最常见的踩坑点。
3. 浮点数据编码
- 每个参数 = 32-bit IEEE-754 float,占两个相邻 16-bit 寄存器。
- 字序(word order)= 大端:高寄存器在前。
- 字节序(byte order)= 大端:寄存器内高字节在前。
- 即整体就是标准大端 float(
>f),4 字节顺序 =[Reg1 Hi][Reg1 Lo][Reg2 Hi][Reg2 Lo]。
实例(来自 PDF)
| 含义 | 原始 4 字节 (hex) | 解码值 |
|---|---|---|
| Volts 1 | 43 66 33 34 |
230.2 V |
| Demand Time | 3F 80 00 00 |
1.0 |
| Network Node | 42 70 00 00 |
60.0 |
Python 解码:
struct.unpack('>f', bytes([0x43,0x66,0x33,0x34]))[0]→230.2。 pymodbus 用BinaryPayloadDecoder.fromRegisters(regs, byteorder=Endian.BIG, wordorder=Endian.BIG)。
4. 输入寄存器表(FC 04 读测量值)
全部为 Float,长度 4 字节,每项占 2 个寄存器。"Hex 起始"是 Modbus 协议起始地址(即 Start Address Hi/Lo)。
| 寄存器 | 参数 | 单位 | Hex 起始 |
|---|---|---|---|
| 30001 | Voltage(电压) | Volts | 0000 |
| 30007 | Current(电流) | Amps | 0006 |
| 30013 | Active power(有功功率) | Watts | 000C |
| 30019 | Apparent power(视在功率) | VA | 0012 |
| 30025 | Reactive power(无功功率) | VAr | 0018 |
| 30031 | Power factor(功率因数) | — | 001E |
| 30071 | Frequency(频率) | Hz | 0046 |
| 30073 | Import active energy(导入有功电能) | kWh | 0048 |
| 30075 | Export active energy(导出有功电能) | kWh | 004A |
| 30077 | Import reactive energy(导入无功电能) | kvarh | 004C |
| 30079 | Export reactive energy(导出无功电能) | kvarh | 004E |
| 30085 | Total system power demand | W | 0054 |
| 30087 | Maximum total system power demand | W | 0056 |
| 30089 | Import system power demand | W | 0058 |
| 30091 | Maximum import system power demand | W | 005A |
| 30093 | Export system power demand | W | 005C |
| 30095 | Maximum export system power demand | W | 005E |
| 30259 | Current demand | Amps | 0102 |
| 30265 | Maximum current demand | Amps | 0108 |
| 30343 | Total active energy(总有功电能) | kWh | 0156 |
| 30345 | Total reactive energy(总无功电能) | kvarh | 0158 |
读取分块建议:0x0000–0x005E(30001–30095)地址连续,可一次块读;0x0102/0x0108、0x0156/0x0158 各为独立小块。整表用 2–3 次块读即可覆盖,减少 Modbus 事务数。
常用核心子集(日常监控够用)
电压 0000、电流 0006、有功功率 000C、功率因数 001E、频率 0046、导入有功电能 0048、导出有功电能 004A、总有功电能 0156。
5. 保持寄存器表(FC 03 读 / FC 16 写配置)
| 寄存器 | 参数 | Hex 起始 | 格式 | 说明 |
|---|---|---|---|---|
| 40013 | Relay Pulse Width | 000C |
Float | 继电器脉宽 60/100/200 ms,默认 100ms |
| 40019 | Network Parity Stop | 0012 |
Float | 0=1停止位无校验(默认),1=1停止位偶校验,2=1停止位奇校验,3=2停止位无校验;改后需重启生效 |
| 40021 | Meter ID | 0014 |
Float | 从机地址 1–247,默认 1 |
| 40029 | Baud rate | 001C |
Float | 0=2400(默认),1=4800,2=9600,5=1200 |
| 40087 | Pulse 1 output mode | 0056 |
Float | 0001 导入有功,0002 导入+导出有功,0004 导出有功(默认),0005 导入无功,0006 导入+导出无功,0008 导出无功 |
| 463745 | Time of scroll display | F900 |
HEX 2字节 | 滚动显示时间 0–30s,默认 0(不滚动) |
| 463761 | Pulse 1 output | F910 |
HEX 2字节 | 0000:0.001kWh/imp(默认),0001:0.01,0002:0.1,0003:1 kWh/imp |
| 463777 | Measurement mode | F920 |
HEX 2字节 | 1:total=import,2:total=import+export(默认),3:total=import-export |
| 464513 | Serial number | FC00 |
uint32 4字节 | 序列号,只读 |
| 464515 | Meter code | FC02 |
Hex 2字节 | 设备码=0020,只读 |
| 464516 | Software version | FC03 |
Hex 2字节 | 软件版本,只读 |
⚠️ 写保持寄存器(改 Meter ID / 波特率 / 校验位等)会改变电表通信参数,配错可能导致通信中断。本项目默认只读采集,不建议在自动化链路里写电表配置。
6. 给本项目采集驱动的要点小结
- 走 Modbus TCP 网关:
ModbusTcpClient(host, port),slave=<Meter ID>。 - 测量值用 FC 04 / 输入寄存器,按 §4 地址读。
- 解码 大端 float32(word & byte 都大端,高寄存器在前)。
- 起始地址与数量都用偶数(成对读)。
- 默认只读,不写电表配置寄存器。
- 不同型号电表 → 不同"寄存器 profile"。本表是
sdm120这一个 profile 的定义。