# 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. 给本项目采集驱动的要点小结 1. 走 **Modbus TCP 网关**:`ModbusTcpClient(host, port)`,`slave=`。 2. 测量值用 **FC 04 / 输入寄存器**,按 §4 地址读。 3. 解码 **大端 float32**(word & byte 都大端,高寄存器在前)。 4. 起始地址与数量**都用偶数**(成对读)。 5. 默认**只读**,不写电表配置寄存器。 6. 不同型号电表 → 不同"寄存器 profile"。本表是 `sdm120` 这一个 profile 的定义。