Example: M-Bus (Meter Bus) integration in LogicMachine platform

Task

Use LogicMachine with external Serial-MBus gateway to gather M-Bus meter readings. Later this data can be transmitted into KNX, BACnet, ModBus and other protocols; or can be used for visualization / trend purpose; or can be used for external cloud solutions.

Devices needed

We used the following components for M-Bus integration into LogicMachine:

lm5-1d

USB-to-RS232-RS485-UART-TTL-Signal-Converter-DZ-074

Techbase_Mbus10_RS232_png

Maddalena_Mbus_meter_png

Packages

Starting from firmware 2016.09 MBus packages are included in the firmware.

Library documentation

https://openrb.com/docs/mbus.htm

Examples

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

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 Lua table where key is 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 scan.

Source code    
  1. -- load mbus library
  2. require('mbus')
  3. -- use /dev/ttyUSB0 serial port with 2400 baud rate
  4. mbus.init('/dev/ttyUSB0', 2400)
  5. -- scan from range 0 to 5 (inclusive)
  6. res = mbus.scan(0, 5)
  7. -- save result to Logs tab
  8. 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.

Source code    
  1. -- load mbus library
  2. require('mbus')
  3. -- use /dev/ttyUSB0 serial port with 2400 baud rate
  4. mbus.init('/dev/ttyUSB0', 2400)
  5. -- new address to write
  6. addr = event.getvalue()
  7. -- change short address from default 0 to new one
  8. res, err = mbus.writeaddr(0, addr)
  9.  
  10. -- change ok
  11. if res then
  12.  alert('[mbus] changed short address to' .. addr)
  13. -- change failed
  14. else
  15.  alert('[mbus] failed to change short address to ' .. addr .. ' ' .. tostring(err))
  16. 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 previous read completes or you will get collision errors.

Source code    
  1. -- load mbus library
  2. require('mbus')
  3. -- use /dev/ttyUSB0 serial port with 2400 baud rate
  4. mbus.init('/dev/ttyUSB0', 2400)
  5. -- new address to write
  6. addr = event.getvalue()
  7. -- read all data
  8. res, err = mbus.getdata(addr)
  9.  
  10. -- read ok
  11. if res then
  12.  log(res)
  13. -- read failed
  14. else
  15.  alert('[mbus] failed to read data from short address ' .. addr .. ' ' .. tostring(err))
  16. end

The return value will look similar to this:

Source code    
  1. * table:
  2. [data]
  3.  * table:
  4.   [0]
  5.    * table:
  6.     [timestamp]
  7.      * string: 1470292199
  8.     [unit]
  9.      * string: Fabrication number
  10.     [dif]
  11.      * string: 14
  12.     [value]
  13.      * string: 0
  14.     [vif]
  15.      * string: 120
  16.     [function]
  17.      * string: Instantaneous value
  18.     [storagenumber]
  19.      * string: 0
  20.   [1]
  21.    * table:
  22.     [timestamp]
  23.      * string: 1470292199
  24.     [unit]
  25.      * string: Time Point (time & date)
  26.     [dif]
  27.      * string: 4
  28.     [value]
  29.      * string: 2016-08-04T07:27:00
  30.     [vif]
  31.      * string: 109
  32.     [function]
  33.      * string: Instantaneous value
  34.     [storagenumber]
  35.      * string: 0
  36.   [2]
  37.    * table:
  38.     [timestamp]
  39.      * string: 1470292199
  40.     [unit]
  41.      * string: Volume (m m^3)
  42.     [dif]
  43.      * string: 4
  44.     [value]
  45.      * string: 27
  46.     [vif]
  47.      * string: 19
  48.     [function]
  49.      * string: Instantaneous value
  50.     [storagenumber]
  51.      * string: 0
  52. ...

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.

Source code    
  1. -- config init
  2. if not meters then
  3.  require('mbus')
  4.  
  5.  -- time to wait between each meter read
  6.  sleeptime = 10
  7.  -- use /dev/ttyUSB0 serial port with 2400 baud rate
  8.  mbus.init('/dev/ttyUSB0', 2400)
  9.  -- base address for meter values, meter short address will be added to form the meters group address
  10.  base = '2/1/'
  11.  
  12.  -- meter definition
  13.  -- addr - short address
  14.  -- id - number from data table where value resides
  15.  -- div - optional divisor for convertion to the final value
  16.  meters = {
  17.    { addr = 1, id = 2, div = 1000 }, -- hot water meter, convert from m3 to liters
  18.    { addr = 2, id = 2, div = 1000 }, -- cold water meter, convert from m3 to liters
  19.    { addr = 3, id = 5 }, --  heating meter 1
  20.    { addr = 4, id = 5 }, --  heating meter 2
  21.  }
  22.  
  23.  -- reset meter values on first run
  24.  for _, meter in ipairs(meters) do
  25.    meter.value = 0
  26.  end
  27. end
  28.  
  29. -- read each meter
  30. for _, meter in ipairs(meters) do
  31.  res = mbus.getdata(meter.addr)
  32.  
  33.  -- read ok
  34.  if type(res) == 'table' then
  35.    data = res.data[ meter.id ]
  36.    value = nil
  37.  
  38.    -- get current value
  39.    if type(data) == 'table' then
  40.      value = tonumber(data.value)
  41.    end
  42.  
  43.    -- value is valid and different from previous read
  44.    if value and meter.value ~= value then
  45.      -- apply divisor if set
  46.      div = meter.div
  47.      dpt = div and dt.float32 or dt.uint32
  48.      if div then
  49.        value = value / div
  50.      end
  51.  
  52.      -- update group address value
  53.      grp.update(base .. tostring(meter.addr), value, dpt)
  54.      meter.value = value
  55.    end
  56.  end
  57.  
  58.  -- wait for next read
  59.  os.sleep(sleeptime)
  60. end

Whole setup

MBus_LogicMachine