M-Bus (Meter Bus)

M-Bus (Meter Bus) integration #

Task #

Use LogicMachine with an external serial to M-Bus gateway to read M-Bus meter readings.

Devices needed #

The following components are used:

  • LogicMachine with RS-232 or RS-485 serial port

M-Bus lm5-1d-300x350

  • M-Bus to RS-232 or RS-485 gateway (for example Techbase Mbus 10)

M-Bus Techbase_Mbus10_RS232_png

  • M-Bus meter (for example Maddalena 1.ETRMD.0340 M-bus water meter)

M-Bus Maddalena_Mbus_meter_png

Library documentation #

https://kb.logicmachine.net/libraries/mbus/

Examples #

Note! As with any scripts which are using serial ports, make sure that only one script is running at any moment of time.

You might need to change the serial port (/dev/RS232) and baud rate (2400) depending on the setup. Meter documentation should state the default baud rate.

1. Scan the bus #

If all short addresses are already programmed, you can scan the bus to find all connected meters. Scan function will return a Lua table where the key is the meter's short address and value is the scan result. True means that the meter is working correctly, false means that probably several meters share the same short address which caused a collision during the scan.

-- load mbus library
require('mbus')
-- use /dev/RS232 serial port with 2400 baud rate
mbus.init('/dev/RS232', 2400)
-- scan from range 0 to 5 (inclusive)
res = mbus.scan(0, 5)
-- save result to Logs tab
log(res)

2. Change short address of a Meter #

For small installations you can connect meters one by one and change short addresses for each meter via an event script mapped to a 1-byte unsigned object. Object value is the new short address to set. Script assumes that all new meters have a default short address of 0.

-- load mbus library
require('mbus')
-- use /dev/RS232 serial port with 2400 baud rate
mbus.init('/dev/RS232', 2400)
-- new address to write
addr = event.getvalue()
-- change short address from default 0 to new one
res, err = mbus.writeaddr(0, addr)

-- change ok
if res then
  alert('[mbus] changed short address to' .. addr)
-- change failed
else
  alert('[mbus] failed to change short address to ' .. addr .. ' ' .. tostring(err))
end

3. Reading meter data #

You can read all single meter data via an event script mapped to a 1-byte unsigned object. Object value is the short address to read, make sure to wait before the previous read completes or you will get collision errors.

-- load mbus library
require('mbus')
-- use /dev/RS232 serial port with 2400 baud rate
mbus.init('/dev/RS232', 2400)
-- new address to write
addr = event.getvalue()
-- read all data
res, err = mbus.getdata(addr)

-- read ok
if res then
  log(res)
-- read failed
else
  alert('[mbus] failed to read data from short address ' .. addr .. ' ' .. tostring(err))
end

The return value will look similar to this:

* table:
 [data]
  * table:
   [0]
    * table:
     [timestamp]
      * string: 1470292199
     [unit]
      * string: Fabrication number
     [dif]
      * string: 14
     [value]
      * string: 0
     [vif]
      * string: 120
     [function]
      * string: Instantaneous value
     [storagenumber]
      * string: 0
   [1]
    * table:
     [timestamp]
      * string: 1470292199
     [unit]
      * string: Time Point (time & date)
     [dif]
      * string: 4
     [value]
      * string: 2016-08-04T07:27:00
     [vif]
      * string: 109
     [function]
      * string: Instantaneous value
     [storagenumber]
      * string: 0
   [2]
    * table:
     [timestamp]
      * string: 1470292199
     [unit]
      * string: Volume (m m^3)
     [dif]
      * string: 4
     [value]
      * string: 27
     [vif]
      * string: 19
     [function]
      * string: Instantaneous value
     [storagenumber]
      * string: 0
  ...

For this meter, current value resides at id 2, but it will be different for different meter models.

4. Gathering data from multiple meters #

You can use this resident script to read values from meters and update object values as soon as meter value changes. Object datatype should be set to 4-byte unsigned integer when divisor is not set, otherwise it should be set to 4-byte floating point. Refer to the previous example on how to find the id value at which the current value resides.

-- config init
if not meters then
  require('mbus')

  -- time to wait between each meter read
  sleeptime = 10
  -- use /dev/RS232 serial port with 2400 baud rate
  mbus.init('/dev/RS232', 2400)
  -- base address for meter values, meter short address will be added to form the meters group address
  base = '2/1/'

  -- meter definition
  -- addr - short address
  -- id - number from data table where value resides
  -- div - optional divisor for convertion to the final value
  meters = {
    { addr = 1, id = 2, div = 1000 }, -- hot water meter, convert from m3 to liters
    { addr = 2, id = 2, div = 1000 }, -- cold water meter, convert from m3 to liters
    { addr = 3, id = 5 }, --  heating meter 1
    { addr = 4, id = 5 }, --  heating meter 2
  }

  -- reset meter values on first run
  for _, meter in ipairs(meters) do
    meter.value = 0
  end
end

-- read each meter
for _, meter in ipairs(meters) do
  res = mbus.getdata(meter.addr)

  -- read ok
  if type(res) == 'table' then
    data = res.data[ meter.id ]
    value = nil

    -- get current value
    if type(data) == 'table' then
      value = tonumber(data.value)
    end

    -- value is valid and different from previous read
    if value and meter.value ~= value then
      -- apply divisor if set
      div = meter.div
      dpt = div and dt.float32 or dt.uint32
      if div then
        value = value / div
      end

      -- update group address value
      grp.update(base .. tostring(meter.addr), value, dpt)
      meter.value = value
    end
  end

  -- wait for next read
  os.sleep(sleeptime)
end