M-Bus 驱动
概述
M-Bus (Meter-Bus, EN 13757-2/3) 的自研 Java native 实现。可通过 TCP socket 连接到运行环境中常见的 wired M-Bus master 网关或串口-TCP 转换器 (Moxa NPort、USR-TCP232 等),收发标准 M-Bus 帧。
| 项目 | 值 |
|---|---|
opc_type | MBUS |
| 实现类 | plantpulse.driver.protocol.mbus.MBusDriver |
| 库 | (无 — 自研实现。EN 13757-2/3) |
| read | 良好 (REQ_UD2 → RSP_UD) |
| write | 良好 (Slave Selection / Application Reset) |
| 默认端口 | 10001 (Moxa NPort 默认) |
| 安全 | 无 |
类 / 结构
MBusDriver (BaseProtocolDriver)
├── Socket / DataInputStream / OutputStream
├── defaultAddress — primary 1~250 (옵션)
├── synchronized requestUd2 / sendSlaveSelection / sendAppReset
├── MBusFrame — buildSndNke / buildReqUd2 / buildSlaveSelection
│ buildSndUdAppReset / parseLongFrame / parseAck
│ ACK=0xE5, START_LONG=0x68, SECONDARY_ADDRESS=253
├── MBusChecksum — sum(c+a+ci+data) mod 256
├── MBusBcd — encodeIdToBcd4(long), BCD 디코드
└── MBusDataRecord — DIF/VIF 파싱, parseAll(buf, off, len) → List<Record>
(Record { value, multiplier, unit, ... })
connect() :
- TCP 连接 (host:10001)。
- 发送 SND_NKE (Short Frame, C=0x40, A=
default-address)。 - 校验收到的 ACK (0xE5) — 即使无响应也置 connected=true (各网关存在差异)。
wire 格式摘要
Short Frame (SND_NKE / Slave Selection 响应 ACK)
0x10 | C(1) | A(1) | CS(1) | 0x16
ACK 단독 = 0xE5
Long Frame (REQ_UD2 / RSP_UD)
0x68 | L | L | 0x68 | C(1) | A(1) | CI(1) | data... | CS(1) | 0x16
L = C + A + CI + data 의 byte 수
CS = (C+A+CI+data) mod 256
| 命令 | C | CI | 含义 |
|---|---|---|---|
| SND_NKE | 0x40 | — | Initialize slave |
| REQ_UD2 | 0x5B | — | 请求 User data |
| Slave Selection | 0x53 | 0x52 | Secondary addressing |
| App Reset | 0x53 | 0x50 | Application reset |
OPC 注册选项 (options)
| 键 | 含义 | 默认值 |
|---|---|---|
default-address | primary address (1~250) | 1 |
connect-timeout | ms | 3000 |
read-timeout | ms | 3000 |
标签地址格式
Primary addressing
| 表示 | 含义 |
|---|---|
1:0 | primary 1,第一个 record |
5:2 | primary 5,第三个 record |
1 | primary 1,全部 user-data hex |
Secondary addressing
| 表示 | 含义 |
|---|---|
secondary:12345678AB1C0107 | 16 位 hex 字符 (ID 4 + Mfg 2 + Ver 1 + Med 1) |
secondary:<16-hex>:<recordIndex> | 上述 + record 索引 |
select:12345678:43932:1:7 | 十进制别名 (id, mfg, ver, med) |
select:12345678:43932:1:7:0 | + record 索引 |
trySecondaryAddress() 先发送 secondary slave selection → 再以 primary=253 执行 REQ_UD2。
ID 按 EN 13757-3 wire format 的 LSB-first BCD 编码 (MBusBcd.encodeIdToBcd4)。
write 地址格式
| 表示 | 动作 |
|---|---|
secondary:<hex> / select:<id>:<mfg>:<ver>:<med> | 仅发送 Slave selection |
app-reset:<subcode> | 对 primary=default-address 执行 application reset |
app-reset:<primary>:<subcode> | 显式指定 primary |
select-and-reset:<id>:<mfg>:<ver>:<med>:<subcode> | 选择 secondary 后 reset |
subcode 也可以用 address.value 形式传入。
record 解码
MBusDataRecord.parseAll(userData, off=12, len) 基于 DIF/VIF 遍历 user-data (header 12 byte 之后),返回 Record { value, multiplier, unit, ... } 列表。
formatValue(record) :
- 整数 + multiplier=1 →
Long.toString(...) - Number →
value * multiplier(double) - 其他 →
String.valueOf(value)
支持 / 不支持
- ✅ SND_NKE → REQ_UD2 → RSP_UD 周期
- ✅ Primary addressing (1~250)
- ✅ Secondary addressing (slave selection + primary 253 read)
- ✅ Application Reset (CI=0x50)
- ✅ DIF/VIF data record 解析 + multiplier 缩放
- ❌ FCB (frame count bit) toggle / Multi-frame UD2
- ❌ Wireless M-Bus (EN 13757-4)
- ❌ Encryption (mode 5 / 7 — 智能电表)
测试覆盖率
test/java/plantpulse/driver/protocol/mbus/
| 类 | 测试数 |
|---|---|
MBusAckTest | 4 |
MBusBcdTest | 13 |
MBusChecksumTest | 4 |
MBusDataRecordTest | 7 |
MBusDriverAddressTest | 10 |
MBusDriverWriteTest | 9 |
MBusFrameTest | 14 |
MBusSpecExtraTest | 28 |
共 89 个测试。
参考
- EN 13757-2 Physical and link layer
- EN 13757-3 Application layer / DIF/VIF / Secondary addressing
- 代码:
src/plantpulse/driver/protocol/mbus/