Skip to main content

M-Bus Driver

Overview

A proprietary Java native implementation of M-Bus (Meter-Bus, EN 13757-2/3). It connects over a TCP socket to a wired M-Bus master gateway or a serial-to-TCP converter (Moxa NPort, USR-TCP232, etc.) commonly found in production environments, and exchanges standard M-Bus frames.

ItemValue
opc_typeMBUS
Implementation classplantpulse.driver.protocol.mbus.MBusDriver
Library(none — proprietary implementation. EN 13757-2/3)
readGood (REQ_UD2 → RSP_UD)
writeGood (Slave Selection / Application Reset)
Default port10001 (Moxa NPort default)
SecurityNone

Classes / Structure

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() :

  1. TCP connection (host:10001).
  2. Send SND_NKE (Short Frame, C=0x40, A=default-address).
  3. Verify ACK (0xE5) reception — connected=true even without a reply (varies by gateway).

Wire Format Summary

Short Frame (SND_NKE / Slave Selection response 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
CommandCCIMeaning
SND_NKE0x40Initialize slave
REQ_UD20x5BUser data request
Slave Selection0x530x52Secondary addressing
App Reset0x530x50Application reset

OPC Registration Options (options)

KeyMeaningDefault
default-addressprimary address (1–250)1
connect-timeoutms3000
read-timeoutms3000

Tag Address Format

Primary addressing

NotationMeaning
1:0primary 1, first record
5:2primary 5, third record
1primary 1, entire user-data hex

Secondary addressing

NotationMeaning
secondary:12345678AB1C010716 hex characters (ID 4 + Mfg 2 + Ver 1 + Med 1)
secondary:<16-hex>:<recordIndex>above + record index
select:12345678:43932:1:7decimal alias (id, mfg, ver, med)
select:12345678:43932:1:7:0+ record index

trySecondaryAddress() first sends a secondary slave selection → then REQ_UD2 with primary=253. The ID is encoded as LSB-first BCD per the EN 13757-3 wire format (MBusBcd.encodeIdToBcd4).

Write Address Format

NotationAction
secondary:<hex> / select:<id>:<mfg>:<ver>:<med>Send slave selection only
app-reset:<subcode>Application reset to primary=default-address
app-reset:<primary>:<subcode>primary specified explicitly
select-and-reset:<id>:<mfg>:<ver>:<med>:<subcode>Reset after secondary selection

The subcode may also be given as address.value.


Record Decoding

MBusDataRecord.parseAll(userData, off=12, len) walks the user data (after the 12-byte header) based on DIF/VIF and returns a Record { value, multiplier, unit, ... } list.

formatValue(record) :

  • Integer + multiplier=1 → Long.toString(...)
  • Number → value * multiplier (double)
  • Otherwise → String.valueOf(value)

Supported / Not Supported

  • ✅ SND_NKE → REQ_UD2 → RSP_UD cycle
  • ✅ Primary addressing (1–250)
  • ✅ Secondary addressing (slave selection + primary 253 read)
  • ✅ Application Reset (CI=0x50)
  • ✅ DIF/VIF data record parsing + multiplier scaling
  • ❌ FCB (frame count bit) toggle / Multi-frame UD2
  • ❌ Wireless M-Bus (EN 13757-4)
  • ❌ Encryption (mode 5 / 7 — smart meters)

Test Coverage

test/java/plantpulse/driver/protocol/mbus/

ClassTest Count
MBusAckTest4
MBusBcdTest13
MBusChecksumTest4
MBusDataRecordTest7
MBusDriverAddressTest10
MBusDriverWriteTest9
MBusFrameTest14
MBusSpecExtraTest28

89 tests in total.


References

  • EN 13757-2 Physical and link layer
  • EN 13757-3 Application layer / DIF/VIF / Secondary addressing
  • Code: src/plantpulse/driver/protocol/mbus/