Skip to main content
Version: Next

M-Bus

M-Bus Functions

This category contains functions specific to M-Bus devices.

API NameBrief Description
api.mbusTransaction()Sends and receives M-Bus frames. Multiple variants. See below.
api.mbusSetup(baudRate, parity, stopBits, dataBits)Configures the M-Bus communication interface.
api.mbusState(state)Controls the M-Bus circuitry.
api.mbusScan(filter, timeout, mode)Scans for M-Bus devices.
api.mbusFilter()Creates and manages internal table of secondary addresses. Multiple variants. See below.
api.mbusVifDifFilter()Filters received M-Bus frames by given group of bytes (VIF/DIF). Multiple variants. See below.

api.mbusTransaction()

Sends and receives M-Bus frames.

Tip: Ensure M-Bus is enabled by first calling api.mbusState(1).

API NameBrief Description
api.mbusTransaction(msg, timeout, retry)Sends a custom M-Bus message.
api.mbusTransaction(index, timeout, retry)Sends a readout request with the specified secondary address.

api.mbusTransaction(msg, timeout, retry)


api.mbusTransaction(pack.pack("<b4", 0x10, 0x50, 0x30, 0x16), 5000, 1)

Sends a custom M‑Bus message and waits for a response or nil to receive without sending.

Arguments

  • msg (string or nil):
    • string - raw bytes to send (use Lua strings like "\x68..." or pack.pack(...)).
    • nil - do not send, wait only for a response.

Optional

  • timeout (integer, optional): Maximum time in milliseconds to wait for a response (default: device MBUS timeout setting).
  • retry (integer, optional): Number of receive attempts if no data is returned.
    • 0 default.
    • 1 no retry.

Return

  • status (integer): Receive result:
    • > 1 - data length (payload bytes received)
    • 1 - single‑byte response (e.g., ACK 0xE5)
    • 0 - no data received after retries
    • -1 bad filter ID.
    • -2 device not responding to selection.
  • c (integer): M‑Bus C field (Control).
  • a (integer): M‑Bus A field (Address).
  • ci (integer): M‑Bus CI field (Control Information).
  • answer (string): Payload received from the bus (data portion). May be post‑processed by a VIF/DIF filter if enabled on the device.
  • raw (string): Complete M‑Bus frame including header/trailer (e.g., 68 LL LL 68 C A CI DATA CS 16).

Examples

-- Send a custom M-Bus message and wait for the response
api.mbusState(1) -- Turn on M-Bus
local msg = pack.pack("<b4", 0x10, 0x50, 0x30, 0x16)
local status, c, a, ci, answer, raw = api.mbusTransaction(msg, 5000, 2)
api.mbusState(0) -- Turn off M-Bus

-- Receive-only for 500 ms (no transmit)
local status, c, a, ci, answer, raw = api.mbusTransaction(nil, 500)

-- Also works (empty string => no transmit)
local status, c, a, ci, answer, raw = api.mbusTransaction("", 500)

api.mbusTransaction(index, timeout, retry)


api.mbusTransaction(0, 3000, 1)

Sends a readout request to the device whose secondary address is referenced by the given filter index.

Arguments

  • index (integer): Index of the secondary address in the table created by api.mbusFilter() (starting with 0).

Optional

  • timeout (integer, optional): Maximum time in milliseconds to wait for a response. Default: device M-Bus timeout setting.
  • retry (integer, optional): Number of receive attempts if no data is returned (Default: 1 - no retry).*

Return

  • status (integer): Receive result:
    • > 1 - payload length (bytes received)
    • 1 - single-byte response (e.g., ACK 0xE5)
    • 0 - no data received after retries
    • < 0 - error:
      • -1 - bad filter index
      • -2 - device not responding during secondary-address selection
      • other negatives - transport-layer codes
  • c (integer): M-Bus C field (Control).
  • a (integer): M-Bus A field (Address).
  • ci (integer): M-Bus CI field (Control Information).
  • answer (string): Payload (data portion) of the response. May be post-processed by a VIF/DIF filter; length can differ from status.
  • raw (string): Complete M-Bus frame including header/trailer (e.g., 68 LL LL 68 C A CI DATA CS 16).

Example

-- Send a readout request using a secondary address (index 0)
api.mbusState(1) -- Turn on M-Bus
local status, c, a, ci, answer, raw = api.mbusTransaction(0, 3000, 1)
api.mbusState(0) -- Turn off M-Bus
print(status, raw)

api.mbusSetup(baudRate, parity, stopBits, dataBits)


api.mbusSetup(9600, 2, 2, 8)

Configures the M-Bus communication interface.

Tip: After configuring the M-Bus parameters with this function, activate the M-Bus by calling api.mbusState(1).

Arguments

  • baudrate (integer): Baudrate for communication (up to 921600 baud).
  • parity (integer): Parity setting:
    • 0 for none.
    • 1 for odd.
    • 2 for even.
  • stopBits (integer): Number of stop bits:
    • 1
    • 2
  • dataBits (integer): Number of data bits:
    • 7
    • 8

Example

-- Configure M-Bus interface to 9600 baud, 8E2
api.mbusSetup(9600, 2, 2, 8)

--------------------------------

if state==1 then
api.mbusSetup(baudR, parity, stopB, dataBits)
print(mbh,"Baud rate: "..baudR,"Parity: ".. parity,"Stop byte: ".. stopB,"Data byte: ".. dataBits)

api.mbusState(state)


api.mbusState(1)

Controls the M-Bus circuitry.

Info: Use api.mbusState(1) before api.mbusTransaction() and api.mbusState(0) after to reduce consumption. The consumption significantly increases if the circuitry is turned on for too long.

Danger: Do not use api.mbusState(1) during LoRaWAN or NB-IoT message transmission.

Controls the M-Bus circuitry.

Arguments

  • state (integer): New state of M-Bus circuitry:
    • 1 for on.
      • Approximately 30 seconds is needed to turn on the M-Bus circuitry.
    • 0 for off.

Return

  • UL (integer): Approximate UL value when turning on (valid only for new topology, otherwise no return value).

Example

-- Turn on M-Bus
api.mbusState(1)

--------------------------------

function onWake ()

if transmissionCounter % mbusRefreshEverNWake == 0 then
print("Refresh MBus frame by readout...")
-- set link parameters - 2400 baud, 8E1
api.mbusSetup(baudrate,parity,stopBits,dataBits)
api.mbusState(1)
api.delayms(initialDelay)

api.mbusScan(filter, timeout, mode)


api.mbusScan()

Scans for M-Bus devices.

Arguments

Optional

  • filter (string, optional): Filter by: identification, manufacturer, version, medium. If no option is selected, scans for everything.
  • timeout (integer, optional): Timeout in milliseconds, default is 3000.
  • mode (integer, optional): If 1, scan in range 0-E instead of default 0-9 (1 for true, 0 for false).

Return

  • var (table of variables): Table of variables (identification, manufacturer, version, medium).
  • cnt (integer): Number of found devices.

Example

-- Scan for M-Bus devices
api.mbusSetup(2400, 2, 1, 8)
api.mbusState(1) -- Turn on M-Bus
e, cnt = api.mbusScan() -- Scan for everything with default timeout
for i = 1, cnt do
print(string.format("%08X", (e[i].identification)))
end
api.mbusState(0) -- Turn off M-Bus

api.mbusFilter()

Creates and manages internal table of secondary addresses.

API NameBrief Description
api.mbusFilter("purge")Purges the table, removing all stored secondary addresses.
api.mbusFilter("populate", filter)Populates the table with a set of secondary addresses.
api.mbusFilter("show")Displays all secondary addresses currently in the table.
api.mbusFilter("fetch")Fetches the table from EEPROM.
api.mbusFilter("scan", filter, save, timeout, scan_mode, useBatteryMonitor, initialDelay, batteryMonitorTimeout, targetVoltage, minimalVoltage, minimalRaise)Performs a scan of devices using secondary addressing.

api.mbusFilter("purge")


api.mbusFilter("purge")

Purges the table, removing all stored secondary addresses.

Arguments

  • "purge" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • status (integer): 0 on success, negative values on failure.

Example

-- Purge M-Bus filter
api.mbusFilter("purge")

if cfg == nil or (as and #cfg < 2) or (not as and #cfg < 4) then
api.mbusFilter("purge")
FLen=0
api.setVar(1, FLen)

api.mbusFilter("populate", filter)


api.mbusFilter("populate", pack.pack("<I", 0x22003287))

Populates the table with a set of secondary addresses provided as input.

Arguments

  • "populate" (string, command)

    ❗command - the argument needs to have this exact form❗

  • filter (string): Array of secondary address IDs.

Return

  • number (integer): Number of secondary addresses populated, negative values on failure.

Example

-- Populate M-Bus filter with one secondary address
x = pack.pack("<I", 0x22003287) -- Store little-endian unsigned integer value
api.mbusFilter("populate", x)
status, _, _, _, _, raw = api.mbusTransaction(0, 3000, 1)
api.dumpArray(raw)

api.mbusFilter("show")


api.mbusFilter("show")

Displays all secondary addresses currently in the table.

Arguments

  • "show" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • number (integer): Number of secondary addresses in the table, 0 if empty.
  • addresses (string): Secondary addresses in the table, empty string if empty.

Example

-- Show M-Bus filter
number, addresses = api.mbusFilter("show")
api.dumpArray(addresses)

api.mbusFilter("fetch")


api.mbusFilter("fetch")

Fetches the table from EEPROM, retrieving the previously saved secondary addresses.

Arguments

  • "fetch" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • status (integer):
    • 0 for success.
    • negative values for failure.
  • number (integer): Number of secondary addresses in the table.

Example

-- Fetch M-Bus filter
status, number = api.mbusFilter("fetch")
print("Number of secondary addresses: " .. number)

--------------------------------

_, fLen = api.mbusFilter("fetch")
api.setVar(1, fLen)

api.mbusFilter("scan", filter, save, timeout, scan_mode, useBatteryMonitor, initialDelay, batteryMonitorTimeout, targetVoltage, minimalVoltage, minimalRaise)


api.mbusFilter("scan", "")

Performs a scan of devices using secondary addressing, based on the filter provided.

Arguments

  • "scan" (string, command)

    ❗command - the argument needs to have this exact form❗

  • filter (string): Array of IDs to scan for.

Optional

  • save (integer, optional): 1 to save the found addresses in the filter.
  • timeout (integer, optional): Timeout in milliseconds.
  • scan_mode (integer, optional): Mode for the scan operation.
  • useBatteryMonitor (integer, optional): 1 to enable battery monitoring.
  • initialDelay (integer, optional): Initial delay before scanning in milliseconds.
  • batteryMonitorTimeout (integer, optional): Timeout for battery monitoring in milliseconds.
  • targetVoltage (integer, optional): Target voltage for the battery monitor.
  • minimalVoltage (integer, optional): Minimal voltage for the battery monitor.
  • minimalRaise (integer, optional): Minimal raise for the battery monitor.

Return

  • addresses (string): Found secondary addresses.

Example

-- Scan M-Bus filter
filter = ""
ids = {0x22003287, 0x22004567, 0x22005678}
for _, id in ipairs(ids) do
filter = filter .. pack.pack("<I", id)
end
save = 1
raw = api.mbusFilter("scan", filter, save)
api.dumpArray(raw)

api.mbusVifDifFilter()

Filters received M-Bus frames by given group of bytes (VIF/DIF).

API NameBrief Description
api.mbusVifDifFilter("purge")Purges the filter.
api.mbusVifDifFilter("populate", filters)Populates the filter with a set of VIF/DIF filters.
api.mbusVifDifFilter("show")Displays all VIF/DIF filters currently in the filter.
api.mbusVifDifFilter("fetch")Fetches the filter from EEPROM.
api.mbusVifDifFilter("activate", index)Activates specified filter.
api.mbusVifDifFilter("deactivate")Deactivates all filters.
api.mbusVifDifFilter("activated")Returns the index of the activated filter.
api.mbusVifDifFilter("filter", mbusFrame, mbusFilter)Filters the M-Bus frame by given VIF/DIF filter.
api.mbusVifDifFilter("filter", mbusFrame, skipIndexes, sizeLimit)Filters the M-Bus frame by maximum size and skip indexes.

api.mbusVifDifFilter("purge")


api.mbusVifDifFilter("purge")

Purges the filter.

Arguments

  • "purge" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • status (integer): 0 on success.

Example

-- Purge M-Bus VIF/DIF filter
api.mbusVifDifFilter("purge")

api.mbusVifDifFilter("populate", filters)


api.mbusVifDifFilter("populate", pack.pack("b2", 0x02, 0x03))

Populates the filter with a set of VIF/DIF filters provided as input.

Arguments

  • "populate" (string, command)

    ❗command - the argument needs to have this exact form❗

  • filters (string): Array of VIF/DIF filters.

Return

  • number (integer): Length of the filter, negative values on failure.

Example

-- Populate M-Bus VIF/DIF filter
api.mbusSetup(2400, 2, 1, 8)
api.mbusState(1)
api.delayms(2000)
filters = pack.pack("b18", 0x02, 0x03, 0xFD, 0xDC, 0xFF, 0x03, 0xFD, 0xC9, 0xFF,
0x02, 0x03, 0xFD, 0xDA, 0xFF, 0x03, 0xFD, 0xC8, 0xFF)
api.mbusVifDifFilter("populate", filters)
api.mbusVifDifFilter("activate", 0)
b = pack.pack('<b5', 0x10, 0x7B, 0xFE, (0x7B + 0xFE) % 256, 0x16)
status, _, _, _, _, raw = api.mbusTransaction(b, 3000, 1)
api.mbusState(0)
print("From M-Bus Calorimeter: ")
api.dumpArray(raw)

--------------------------------

function loadConfiguration()
vifDifFilterLength = api.getVar(20, -1)

if vifDifFilterLength == -1 then
api.setVar(22, "bytes", defaultVifDifFilter)
api.setVar(20, #defaultVifDifFilter, true)
vifDifFilterLength = #defaultVifDifFilter

end
if vifDifFilterLength ~= 0 then
api.mbusVifDifFilter("populate", api.getVar(22, "bytes", vifDifFilterLength))
end

api.mbusVifDifFilter("show")


api.mbusVifDifFilter("show")

Displays all VIF/DIF filters currently in the filter.

Arguments

  • "show" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • number (integer): Length of the filter, 0 if empty.
  • filters (string): VIF/DIF filter entries, empty if none.

Example

-- Show M-Bus VIF/DIF filter
number, filters = api.mbusVifDifFilter("show")
api.dumpArray(filters)

api.mbusVifDifFilter("fetch")


api.mbusVifDifFilter("fetch")

Fetches the filter from EEPROM, retrieving the previously saved VIF/DIF filters.

Arguments

  • "fetch" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • status (integer): 0 on success, negative values on failure.
  • number (integer): Length of the filter.

Example

-- Fetch M-Bus VIF/DIF filter
status, number = api.mbusVifDifFilter("fetch")
print("Number of VIF/DIF filters: " .. number)

api.mbusVifDifFilter("activate", index)


api.mbusVifDifFilter("activate", 0)

Activates specified filter.

Arguments

  • "activate" (string, command)

    ❗command - the argument needs to have this exact form❗

Optional

  • index (integer, optional): Index of the filter to activate; if nothing or -1 is provided, all filters are deactivated.

Return

  • status (integer):
    • 0 on success.
    • -1 on failure.

Example

-- Activate M-Bus VIF/DIF filter
status = api.mbusVifDifFilter("activate", 0)
print("Activated filter status: " .. status)

api.mbusVifDifFilter("deactivate")


api.mbusVifDifFilter("deactivate")

Deactivates all filters.

Arguments

  • "deactivate" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • status (integer): 0 on success.

Example

-- Deactivate M-Bus VIF/DIF filter
api.mbusVifDifFilter("deactivate")

api.mbusVifDifFilter("activated")


api.mbusVifDifFilter("activated")

Returns the index of the activated filter.

Arguments

  • "activated" (string, command)

    ❗command - the argument needs to have this exact form❗

Return

  • index (integer):
    • Index of activated filter.
    • -1 if none.

Example

-- Check activated M-Bus VIF/DIF filter
index = api.mbusVifDifFilter("activated")
print("Activated filter index: " .. index)

api.mbusVifDifFilter("filter", mbusFrame, mbusFilter)


api.mbusVifDifFilter("filter", "frame", "filter")

Filters the M-Bus frame by given VIF/DIF filter.

Arguments

  • "filter" (string, command)

    ❗command - the argument needs to have this exact form❗

  • mbusFrame (string): M-Bus frame to filter.

  • mbusFilter (string): VIF/DIF filter to apply.

Return

  • filteredFrame (string): Filtered M-Bus frame.

Example

-- Filter M-Bus frame with VIF/DIF filter
f = pack.pack("b61",
0x68, 0x59, 0x59, 0x68, 0x08, 0x03, 0x72, 0x64, 0x05, 0x03, 0x41, 0xAE, 0x4C, 0x49, 0x07, 0x7C, 0x00, 0x00, 0x00, 0x0C, 0x14, 0x00, 0x96, 0x06, 0x00, 0x0C, 0x94, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x78, 0x64, 0x05, 0x03, 0x41, 0x0C, 0xFD, 0x10, 0x64, 0x05, 0x03, 0x41, 0x04, 0x6D, 0x32, 0x0D, 0x3F, 0x31, 0x04, 0xFD, 0x17, 0x10, 0x00, 0x00, 0x00, 0x84, 0x0F, 0x6D, 0x3B, 0x17, 0x1F, 0x3C, 0x8C, 0x0F, 0x14, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x0F, 0x6D, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x0F, 0x14, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xB1, 0x16
)
filter = pack.pack("b7", 0x02, 0x02, 0x0C, 0x14, 0x02, 0x0C, 0x78)
filteredFrame = api.mbusVifDifFilter("filter", f, filter)
api.dumpArray(filteredFrame, "raw")

api.mbusVifDifFilter("filter", mbusFrame, skipIndexes, sizeLimit)


api.mbusVifDifFilter("filter", "frame", 2, 51)

Filters the M-Bus frame by maximum size and skip indexes.

Arguments

  • "filter" (string, command)

    ❗command - the argument needs to have this exact form❗

  • mbusFrame (string): M-Bus frame to filter.

  • skipIndexes (integer): Number of items to skip.

  • sizeLimit (integer): Maximum size of the filtered frame.

Return

  • filteredFrame (string): Filtered M-Bus frame with applied callback filter.
  • lastAcceptedIndex (integer): Index of the last accepted item.
  • highestIndex (integer): Highest index found during filtering.
  • isDone (boolean):
    • true if the whole frame is filtered.
    • false if the whole frame was not filtered.

Example

-- Filter M-Bus frame by maximum size
f = pack.pack("b61",
0x68, 0x59, 0x59, 0x68, 0x08, 0x03, 0x72, 0x64, 0x05, 0x03, 0x41, 0xAE, 0x4C, 0x49, 0x07, 0x7C, 0x00, 0x00, 0x00, 0x0C, 0x14, 0x00, 0x96, 0x06, 0x00, 0x0C, 0x94, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x78, 0x64, 0x05, 0x03, 0x41, 0x0C, 0xFD, 0x10, 0x64, 0x05, 0x03, 0x41, 0x04, 0x6D, 0x32, 0x0D, 0x3F, 0x31, 0x04, 0xFD, 0x17, 0x10, 0x00, 0x00, 0x00, 0x84, 0x0F, 0x6D, 0x3B, 0x17, 0x1F, 0x3C, 0x8C, 0x0F, 0x14, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x0F, 0x6D, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x0F, 0x14, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xB1, 0x16
)
filteredFrame, lastAcceptedIndex, highestIndex, isDone = api.mbusVifDifFilter("filter", f, 2, 51)
api.dumpArray(filteredFrame, "raw")
print("Last Accepted Index: " .. lastAcceptedIndex, "Highest Index: " .. highestIndex, "Is Done: " .. tostring(isDone))