Files
home-automation/docs/references/DDSU666-Modbus-Protocol.md
tliu93 3e04b15656
frontend / frontend (push) Successful in 2m21s
pytest / test (push) Successful in 9m50s
docker-image / build-and-push (push) Successful in 4m24s
feat(modbus): add DDSU666 profile and select read function code per profile
- Add CHINT DDSU666 profile (ddsu666.yaml): FC03 holding registers, voltage/
  current/active power (kW)/reactive power (kvar)/PF/frequency, import+export
  active energy. Word/byte order left at big-endian as a documented best guess
  (manual has no float example) — to be confirmed on-device.
- Add DDSU666-Modbus-Protocol.md reference extracted from the official manual,
  plus the source PDF (parity with the SDM120 reference).
- Generalize driver.read_blocks to dispatch FC03 (holding) or FC04 (input)
  based on a function_code argument (default 4, SDM120 behaviour unchanged);
  the code is validated before any connection is attempted.
- Wire profile.function_code through the CLI read command, the background
  poller, and the device /test endpoint — previously the profile field was
  declared but never honoured (read path was hardcoded to FC04).
- Tests: default -> FC04, function_code=3 -> FC03 holding, invalid FC rejected
  before connecting.
2026-06-30 17:16:25 +02:00

199 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DDSU666 Modbus 协议(从官方 PDF 提取)
> 来源:`docs/references/DDSU666 Single phase Smart Meter.pdf`
> CHINT / 正泰仪表 **DDSU666 Single phase Smart Meter — Operation Manual**,文档号 `ZTY0.464.1224`,版本 **V2**2020 年 8 月;厂商 Zhejiang Chint Instrument & Meter Co., Ltd.
> 本文件是 PDF 的可读化提取,供本项目的 Modbus 采集驱动设计参考。**以官方 PDF 为准**,本文件如有出入以 PDF 为准。
> 同类文档见 SDM120 的 `SDM120-Modbus-Protocol.md`;两表差异较大,见下方 §6「与 SDM120 的关键差异」。
## 设备速览(来自手册 Table 1 / Table 5
| 项 | DDSU666(直接接入) | DDSU666-CT(经互感器) |
| --- | --- | --- |
| 精度等级 | Active Class B | Active Class C |
| 参考电压 | 230 V | 230 V |
| 电流规格 | 0.255(80) A | 0.0151.5(6) A |
| 表常数 | 800 imp/kWh | 6400 imp/kWh |
| 接入方式 | 直接接入 | 经电流互感器 |
- 单相电子式电能表,DIN35mm 导轨安装;测量电压、电流、有功/无功功率、频率、功率因数、正/反向有功电能。
- 电能测量范围 `0999999.99 kWh`(LCD 只显示 6 位,自动移动小数点)。
- 通信:RS485**Modbus-RTU**(也支持 DL/T 645-2007,可切换,见 §5 `0005H ChangeProtocol`)。
- 手册 Table 1 标注 Frequency Reference = 60Hz,但 LCD 示例又写 `F=50.00Hz`(手册自身不一致);**实际频率以寄存器 `200EH` 读数为准**,不要把 50/60 写死。
## 0. 本项目的接入方式(重要)
DDSU666 物理层是 **Modbus RTURS-485 串口)**,半双工。和 SDM120 一样,本项目通过一个 **Modbus-TCP 网关**接入:
- 后端用 **Modbus TCP**`IP:port`)连到网关,网关在串口侧转成 RTU 与电表通信。
- TCP 帧用 MBAP header、**无 CRC**CRC 由网关在 RTU 侧处理)。本文档里 RTU 帧的 `CRC (Lo/Hi)` 字段在 TCP 模式下不需要我们关心。
- **Slave Address / Unit ID = 电表的通信地址 Addr**(范围 1–247;面板按键只能设 1–99;见 §5 `0006H`),在 TCP 请求里作为 unit id 传入。
- 若以后直连串口(RTU),才需要管波特率 / 数据格式 / CRC:**默认串口格式是 8 数据位、无校验、2 停止位(8N2)**,与 SDM120 的 8N1 不同——直连时务必对齐。
- 电表必须处于 **Modbus 协议模式**(而非 DL/T 645)才能用本协议;可经面板长按切换,或写 `0005H = 2`(见 §5)。
## 1. 协议帧格式(Appendix A
异步传输,按字节为单位。一帧 10 位字符 = **1 起始位(0) + 8 数据位(无校验) + 2 停止位(1)**(其它格式可定制)。
### 信息帧结构(Table A.1
| 字段 | 长度 | 说明 |
| --- | --- | --- |
| Start(起始) | >3.5 字符静默 | 帧间至少 3.5 字符空闲时间作为分隔 |
| Address code(地址码) | 1 字节 | 目标从机地址 1–247;每个从机在总线上地址唯一 |
| Function code(功能码) | 1 字节 | 仅支持 **03H / 10H**(见 §2 |
| Data(数据域) | n 字节 | 随功能码不同而不同(起始地址、寄存器数、寄存器数据等) |
| CRC check codeCRC 校验) | 2 字节 | 16-bit CRC(**低字节在前、高字节在后**;多项式 `A001` |
| End(结束) | >3.5 字符静默 | 帧间静默 |
> TCP 网关模式下 Start/End 静默与 CRC 由网关处理,本项目不关心。
### 功能码 03H 示例(读寄存器,Table A.3/A.4
读从机 `01H`、起始地址 `0CH`、2 个寄存器:
- 主机发送:`01 03 00 0C 00 02 04 08`(最后 `04 08` 是 CRC,低字节 `04` 在前)。
- 从机返回(设 `0CH/0DH` 内容为 `0000H``1388H`):`01 03 04 00 00 13 88 F7 65`
- `04` = 字节数;`00 00` = `0CH` 数据;`13 88` = `0DH` 数据;`F7 65` = CRC(低字节 `F7` 在前)。
> **注意**:单个 16-bit 寄存器内是「高字节在前、低字节在后」(Table A.4 里 `0DH` 数据返回 `13 88` = `0x1388`)。这一点对解码浮点的字节序很关键,见 §3。
### 功能码 10H 示例(写多个寄存器,Table A.5/A.6
向从机 `01H`、起始地址 `00H` 连续写 3 个寄存器 `0002H,1388H,000AH`
- 主机发送:`01 10 00 00 00 03 06 00 02 13 88 00 0A 9B E9`
- `06` = 写入字节数(3 寄存器 × 2 字节);随后是 3 个寄存器的数据;末尾 `9B E9` CRC。
- 从机返回:`01 10 00 00 00 03 80 08`(回显起始地址 + 寄存器数 + CRC `80 08`)。
### 异常响应(Table A.7/A.8
- 异常时返回的 Function Code = **原功能码 + 128**(即最高位置 1`03H→83H``10H→90H`)。
- 数据为单字节 Error Code
| Error Code | 含义 | 说明 |
| --- | --- | --- |
| `01H` | Illegal function code | 收到的功能码本表不支持 |
| `02H` | Illegal register address | 寄存器地址超出有效范围 |
| `03H` | Illegal data value | 数据值超出对应地址的取值范围 |
## 2. 功能码
DDSU666 **只支持两个功能码**Table A.2):
| 功能码 | 作用 | 说明 |
| --- | --- | --- |
| **03H** | Read register(读寄存器) | 读一个或多个寄存器——**测量值、电量、配置全部走它** |
| **10H** | Write multiple registers(写多个寄存器) | 向 n 个连续寄存器写 n 个 16-bit 数据(改配置 / 清电量) |
> ⚠️ **DDSU666 没有「输入寄存器 / FC04」概念**——所有数据(包括电压电流功率)都用 **FC 03H** 读保持寄存器。这是它和 SDM120(测量值走 FC04)最大的踩坑差异,见 §6。
## 3. 数据编码
DDSU666 有两类数据:
1. **配置 / 参数寄存器(§5`0000H``0010H`**:每个 1 个寄存器、`16-bit with symbols`**16 位有符号整数**)。
2. **测量 / 电量寄存器(§4`2000H`+ / `4000H`+**:每个参数 = **32-bit IEEE-754 单精度浮点**(手册写 “single precision floating decimal”),占 **2 个相邻寄存器**Length = 2 Word)。
**浮点字节序 / 字序**
- **字节序(byte order)= 大端:寄存器内高字节在前** —— 由 Table A.4 的 `0x1388` 返回为 `13 88` 确认。
- **字序(word order,两个寄存器谁是高 16 位)**:手册**没有给出浮点解码的实例**,未明确标注。按标准 Modbus 浮点惯例应为**大端字序(高寄存器在前,`ABCD`)**,与本项目 SDM120 驱动一致(`registers_to_float``>f`,高寄存器在前)。
- ⚠️ **需上机实测确认**:读 `2000H`(电压)应解出 ~230V 这样的合理值;若解出乱数,多半是字序相反,改成「低寄存器在前」再试。CHINT 同系列(DTSU/DDSU666)现场固件偶有字序差异,**首次接入务必用一个已知量(电压)校验**,不要凭手册想当然。
> Python 解码(大端、高寄存器在前):`struct.unpack('>f', struct.pack('>HH', hi_reg, lo_reg))[0]`。
> pymodbus`BinaryPayloadDecoder.fromRegisters(regs, byteorder=Endian.BIG, wordorder=Endian.BIG)`。
## 4. 测量 / 电量寄存器表(FC 03H 读)
全部为只读、`Float`32-bit),每项占 **2 个寄存器**。地址为 Modbus 协议原始地址(即帧里的 Start Register Address Hi/Lo),手册用十六进制。
### 4.1 瞬时量(“Electric quantity of the secondary side”,`2000H` 段)
| 地址(hex) | 参数 | 代号 | 单位 | 备注 |
| --- | --- | --- | --- | --- |
| `2000H` | 电压 Voltage | U | V | |
| `2002H` | 电流 Current | I | A | |
| `2004H` | 有功功率 Active power | P | **kW** | 手册标注 “the unit is KW”——**不是 W** |
| `2006H` | 无功功率 Reactive power | Q | **kvar** | |
| `2008H` | (保留 RESERVED | — | — | 占 2 寄存器,跳过 |
| `200AH` | 功率因数 Power factor | PF | — | 无量纲 |
| `200CH` | (保留 RESERVED | — | — | 占 2 寄存器,跳过 |
| `200EH` | 频率 Frequency | Freq | Hz | |
### 4.2 电量(“Electrical data of the secondary side”,`4000H` 段)
| 地址(hex) | 参数 | 代号 | 单位 | 备注 |
| --- | --- | --- | --- | --- |
| `4000H` | 正向(导入)有功电能 Active in electricity | Ep | kWh | 正向 / forward active energy |
| `400AH` | 反向(导出)有功电能 Reverse in electricity | -Ep | kWh | 反向 / reverse active energy |
> 手册里 `4000H` 与 `400AH` 之间(`4002H``4009H`)未列出,视为保留/未文档化。
**读取分块建议**
- 瞬时量:`2000H``200FH`**16 个寄存器连续**,一次块读即可覆盖 U…Freq(含两段 RESERVED,解码时跳过)。
- 电量:`4000H`2 寄存器)与 `400AH`(2 寄存器)相距较远,分两小块读,或读 `4000H``400BH`(12 寄存器)一次取出后挑用——以网关 / 电表是否允许跨保留地址块读为准,谨慎起见分开读更稳。
### 常用核心子集(日常监控够用)
电压 `2000H`、电流 `2002H`、有功功率 `2004H`kW)、功率因数 `200AH`、频率 `200EH`、正向有功电能 `4000H`、反向有功电能 `400AH`
## 5. 配置 / 参数寄存器表(FC 03H 读 / FC 10H 写)(Table 9
每个 1 个寄存器、**16-bit 有符号整数**。`R/W` 列来自手册。
| 地址(hex) | 代号 | 含义 | R/W | 取值 / 说明 |
| --- | --- | --- | --- | --- |
| `0000H` | UCode | 编程密码 Programming password code | R/W | 写配置前的密码字 |
| `0001H` | REV. | 保留;**实际读出的是版本号** | R | |
| `0002H` | ClrE | 电能清零 CLr.E | R/W | **写 `1` 清除总电量**(不可逆,慎用) |
| `0003H``0004H` | RESERVED | 保留 | — | |
| `0005H` | ChangeProtocol | 协议切换 | R/W | **`2` = Modbus-RTU**`1` = DL/T 645-2007 |
| `0006H` | Addr | 通信地址 | R/W | 1247(面板按键仅 199 |
| `0007H``000AH` | RESERVED | 保留 | — | |
| `000BH` | Meter type | 表型 Meter type | R | 只读设备类型标识 |
| `000CH` | BAud | 通信波特率 | R/W | **`1`=2400bps`2`=4800bps`3`=9600bps**(手册寄存器仅列这三档;通信章另提到也支持 1200bps) |
| `000DH``0010H` | RESERVED | 保留 | — | |
> ⚠️ 写 `0002H`(清电量)、`0006H`(改地址)、`000CH`(改波特率)、`0005H`(切协议)都会改变电表状态或通信参数,配错可能**清空累计电量**或**导致通信中断**。本项目默认**只读采集**,不在自动化链路里写电表配置寄存器。
> 写配置通常需先经 `0000H UCode` 密码字校验;具体密码值手册正文未给出,需向厂商确认或经面板操作。
## 6. 与 SDM120 的关键差异(迁移 / 复用驱动时必看)
本项目已有 SDM120 profile`app/integrations/modbus/profiles/sdm120.yaml`)。DDSU666 **不能照搬**,主要差异:
| 维度 | SDM120 (Eastron) | DDSU666 (CHINT) |
| --- | --- | --- |
| 读测量值功能码 | **FC 04**(输入寄存器 3X | **FC 03**(保持寄存器,无 FC04 |
| 测量值起始地址 | `0x0000` 起(30001 | 瞬时量 `0x2000` 起;电量 `0x4000`/`0x400A` |
| 有功功率单位 | **W**(瓦) | **kW(千瓦)** —— 入库前注意换算 / 单位标注 |
| 无功功率单位 | VAr | kvar |
| 配置寄存器格式 | FloatFC03/16 | **16-bit 有符号整数**FC03/10 |
| 写功能码 | 16 / 0x10 | 10H(同 0x10 |
| 串口默认格式 | 8N1(1 停止位) | **8N22 停止位)** |
| 多协议 | 仅 Modbus | Modbus **与 DL/T 645-2007 可切换**(需确保在 Modbus 模式) |
| 浮点字序 | 大端、高寄存器在前(手册有实例佐证) | 字节序大端已确认;**字序手册无实例,需上机实测** |
## 7. 给本项目采集驱动的要点小结
1.**Modbus TCP 网关**`ModbusTcpClient(host, port)``slave=<Addr>`;电表须在 **Modbus 协议模式**
2. 所有读取(测量 + 电量 + 配置)都用 **FC 03H**——**没有 FC04**。
3. 测量值在 `0x2000` 段、电量在 `0x4000`/`0x400A`,均为 **float32**;解码大端字节序,**字序默认高寄存器在前但务必用电压实测校验**。
4. **有功功率单位是 kW、无功是 kvar**——与 SDM120 的 W/VAr 不同,新建 profile / 入库映射时单位别抄错。
5. 配置寄存器(`0x0000``0x0010`)是 **16-bit 有符号整数**,不是 float。
6. 默认**只读**`0002H` 写 1 会**清空累计电量**、`0006H/000CH/0005H` 会改通信参数,自动化链路里一律不写。
7. 新建 profile 时这是 `ddsu666` 这一个 register profile 的定义;建议 `function_code: 3``word_order: big`(先按大端字序,接入后用电压读数验证)、瞬时量与电量分块读。
## 8. 实测记录(真机验证,2026-06-30)
首次接入一台 **DDSU666 直接接入版(5(80)A** 实测,确认以下几点:
- **字序大端,确认无误**:电压 / 电流 / 频率 / 电能用「大端、高寄存器在前」解码全部得到合理值(如 233.9 V / 0.055 A / 49.99 Hz),与 §3 的假设一致。`ddsu666.yaml``word_order: big` / `byte_order: big` **无需修改**,§3 里「字序需上机实测」一项可视为已关闭。
- **FC03 读通**:所有量走 FC03profile `ddsu666` 在采集链路(CLI `read` / 设备 `/test` / 后台轮询)中工作正常。
- **低电流下「瞬时功率读 0、但电能照常累加」**:测试负载仅为一台 PoE 交换机(≈230 V / 0.05 A,真实有功仅几瓦),**远低于本表测量量程下限 Imin≈0.25 A**。此工况下:
- 瞬时 `active_power``0x2004`)与 `power_factor``0x200A`)寄存器返回**全零**(原始 hex `0x0000 0x0000`);电表 LCD 上功率在 0~3.3 W、PF 在 0~0.6 之间抖动。
- 抖动成因 = **低电流测量噪声 + 开关电源(SMPS,无 PFC)畸变电流**(电流为电压峰值附近的窄脉冲、谐波重 → 畸变功率因数天然偏低)。**不是**「采样率与开关频率拍频」:计量芯片 SH79F7019 采样在 kHz 量级,远低于开关电源 50–200 kHz 的开关频率,两者不在一个频段。
- 但累计电能 `import_energy``0x4000`)**正常累加**(实测 0 → 0.01 kWh)——电表内部积分器在防潜动起始电流(`0.004·Ib`≈0.02 A)之上照常计量。
- **结论**:低于量程下限时**瞬时功率 / PF 不可信,但电能计量不丢**;电流进入量程(正常负载)后瞬时量即稳定可信。这是 5(80)A 大量程表对极小负载的固有特性,**非缺陷、非解码问题**。
- **排错提示**:若日后看到 DDSU666「功率一直 0」,先确认负载电流是否在 Imin 以上——多半是负载太轻而非链路故障;可用 `scripts/modbus_cli probe --fc 3 --address 0x2000 --count 16``0x2004/0x2005` 原始寄存器是否真为全零佐证。