ganehag / pyMeterBus

Pure Python implementation of the Meter-Bus (M-Bus EN13757-3) protocol.
BSD 3-Clause "New" or "Revised" License
78 stars 40 forks source link

Meter-Bus for Python

Build status codecov pypi GitHub issues GitHub issues PyPI Status

About

M-Bus (Meter-Bus) is a European standard (EN 13757-2 physical and link layer, EN 13757-3 application layer) for the remote reading of gas or electricity meters. M-Bus is also usable for other types of consumption meters. The M-Bus interface is made for communication on two wires, making it very cost-effective.

Python version

I've decided only to support active Python version. Thus, any EOL version is not supported.

Current State (2022)

The library works, but it lacks proper documentation. Well, it lacks any documentation, to be honest.

The implementation is currently under heavy development. Its original intended use case was particular, as a library to aid in decoding M-Bus telegrams sent over HTTP, and might thus not suit everyone.

Still, it is a generic library and supports several different use cases.

Currently, only the variable data structures are implemented. The library can only decode M-Bus frames. It does presently NOT support encoding and transmission of M-Bus frames, such as control frames.

However, if the need arises, I might implement missing pieces on a request basis.

Tools

You can find a set of utilities in the tools folder.

These tools can communicate over a serial device /dev/ttyX or even over RFC2217 using the format rfc2217://host:port.

Suppose you are using ser2net as an RFC2217 server. You need to configure it the following way:

2000:telnet:0:/dev/ttySX:2400 remctl banner

Known Issues

What works

Basic API documentation

meterbus.load(data)

Returns an object of either type WTelegramSndNr, TelegramACK, TelegramShort, TelegramControl or TelegramLong. If an error occurs, it will raise an MBusFrameDecodeError.

meterbus.debug(state)

Produces debug messages to stdout.

meterbus.send_ping_frame(ser, address)

Sends a PING frame to address over the serial connection ser.

meterbus.recv_frame(ser, length)

Reads an entire frame and returns the unparsed data.

meterbus.send_request_frame_multi(ser, address, req)

If req is None, build a new request frame using address and send it.

meterbus.send_select_frame(ser, secondary_address)

Sends a select frame with the supplied secondary address.

meterbus.XXX

More to come...

Code examples

Decode the value of a single record (record 3)

#!/usr/bin/python

import meterbus

data = "\x68\x6A\x6A\x68\x08\x01\x72\x43\x53\x93\x07\x65" \
       "\x32\x10\x04\xCA\x00\x00\x00\x0C\x05\x14\x00\x00" \
       "\x00\x0C\x13\x13\x20\x00\x00\x0B\x22\x01\x24\x03" \
       "\x04\x6D\x12\x0B\xD3\x12\x32\x6C\x00\x00\x0C\x78" \
       "\x43\x53\x93\x07\x06\xFD\x0C\xF2\x03\x01\x00\xF6" \
       "\x01\x0D\xFD\x0B\x05\x31\x32\x4D\x46\x57\x01\xFD" \
       "\x0E\x00\x4C\x05\x14\x00\x00\x00\x4C\x13\x13\x20" \
       "\x00\x00\x42\x6C\xBF\x1C\x0F\x37\xFD\x17\x00\x00" \
       "\x00\x00\x00\x00\x00\x00\x02\x7A\x25\x00\x02\x78" \
       "\x25\x00\x3A\x16"

telegram = meterbus.load(data)
print telegram.records[3].parsed_value
~$ 2014-02-19T11:18

Request a frame over Serial and dump it in JSON format

#!/usr/bin/python

import serial
import meterbus

address = 254

with serial.Serial('/dev/ttyACM0', 2400, 8, 'E', 1, 0.5) as ser:
  meterbus.send_ping_frame(ser, address)
  frame = meterbus.load(meterbus.recv_frame(ser, 1))
  assert isinstance(frame, meterbus.TelegramACK)

  meterbus.send_request_frame(ser, address)
  frame = meterbus.load(meterbus.recv_frame(ser, meterbus.FRAME_DATA_LENGTH))
  assert isinstance(frame, meterbus.TelegramLong)

  print(frame.to_JSON())

M-Bus Packet Format

Single Character Short Frame Control Frame Long Frame
E5h Start 10h Start 68h Start 68h
C Field L Field = 3 L Field
A Field L Field = 3 L Field
Check Sum Start 68h Start 68h
Stop 16h C Field C Field
A Field A Field
CI Field CI Field
Check Sum User Data (0-252 Byte)
Stop 16h Check Sum
Stop 16h

License

Please see the LICENSE file