Modbus Binding

The binding supports both TCP and Serial slaves. RTU, ASCII and BIN variants of Serial Modbus are supported.

The binding can act as

  • Modbus TCP Client (that is, as modbus master), querying data from Modbus TCP servers (that is, modbus slaves).
  • Modbus serial master, querying data from modbus serial slaves

The Modbus binding polls the slaves with an configurable poll period. openHAB commands are translated to write requests.

Table of Contents

Binding Configuration

This binding can be configured in the file services/modbus.cfg.

Global configuration

Most of config parameters are related to specific slaves, but some are global and thus affect all slaves.

Property Default Required Description
poll 200 No Poll period (optional)
Frequency of polling Modbus slaves. Note that the value is in milliseconds! For example, poll=1000 makes the binding poll Modbus slaves once per second.
writemultipleregisters false No Function code to use when writing holding registers (optional)
Binding can be configured to use FC 16 (Write Multiple Holding Registers) over FC 6 (Write Single Holding Register) when writing holding register items (see above). This is optional and default is false. For example, writemultipleregisters=true makes the binding to use FC16 when writing holding registers.

Configuration parameters specific to each slave

The slaves are configured using key value pairs in openHAB config file. Each slave (identified by the slave name) in the config corresponds to a single modbus read request. Note that it might be necessary to define many “slaves” in openHAB configuration to read all data from a single physical modbus slave.

The configuration parameters have the following pattern:



  • <slave-type> can be either “tcp” or “serial” depending on the type of this Modbus slave
  • <slave-name> is unique name per slave you are connecting to. Used in openHAB configuration to refer to the slave.
  • <slave-parameter-name> identifies the parameter to configure
  • <slave-parameter-value> is the value of the parameter

Valid slave parameters are

Property Required Description
connection mandatory Connection string for the slave.

TCP slaves use the form host_ip[:port] e.g. or If you omit port, default 502 will be used.

For serial connections use the form port:[:baud:[dataBits:[parity:[stopBits:[encoding]]]]], e.g. /dev/ttyS0:38400:8:none:1:rtu (read from /dev/ttyS0 using baud rate of 38400, 8 databits, no parity, 1 stopbits, and rtu encoding. Another minimal example is /dev/ttyS0:38400 where all optional parameters except baud rate will get their default values.

port refers to COM port name on Windows and serial device path in *nix. Optionally one can configure one or more of the serial parameters: baud (default 9600), dataBits (default 8), parity (default none), stopBits (default 1), encoding (default ascii).
Options for the optional serial parameters are as follows: parity={even, odd}; encoding={ascii, rtu, bin}.
id optional slave id, default 1. Also known as Address, Station address, or Unit identifier, see Wikipedia and simplymodbus articles for more information
type mandatory object type. Dictates the function codes used for read and write. See below for more information. Can be either coil, discrete, holding, or input.
start optional address of first coil/discrete input/register to read. Default is 0. The address is directly passed to the corresponding Modbus request, and thus is zero-based. See below for more information on the addressing.
length mandatory number of data items to read. Data items here refers to registers, coils or discrete inputs depending on the slave type. For example, if the goal is to read one item with valuetype=int32, one needs to read two registers (2 * 16bit = 32bit), thus length = 2. If three coils are of interest, one should specify length = 3
valuetype optional tells how interpret the register data. For details, consult Register interpretation (valuetype) on read & write.
updateunchangeditems optional *Since 1.9.0. true or false. Controls whether the binding sends an update event on every successful poll (true) or only if the state of the item actually changes (false). Default is false. When polling many items with high poll frequency, setting this parameter to true may cause significant CPU usage.
postundefinedonreaderror optional Since 1.9.0. true or false. Controls whether the binding sends Undefined (UnDefType.UNDEF) to the items associated with this slave when a read error occurs. Here read error refers to connection issues (cannot establish connection), I/O error (e.g. uninterrupted connection, unexpected EOF), modbus protocol exceptions (e.g. “Illegal data address”), or response transaction id not matching the request. Note that when updateunchangeditems is enabled, the Undefined is sent only once on errors, unless the slave recovers from the error.

Advanced connection parameters

Since 1.9.0, connection parameter can take additional colon (:) separated parameters:

  • For TCP slave the new format for connection parameter is: host_ip[:port[:interTransactionDelayMillis[:reconnectAfterMillis[:interConnectDelayMillis[:connectMaxTries[:connectTimeout]]]]]].
  • For the serial slaves the new format is: port[:baud[:dataBits[:parity[:stopBits[:encoding[:interTransactionDelayMillis[:receiveTimeoutMillis[:flowControlIn[:flowControlOut]]]]]]]].

Explanation of these new parameters

  • interTransactionDelayMillis: Time to wait between consecutive modbus transactions (to the same host or serial device), in milliseconds. Each modbus transaction corresponds to read or write operation. Default 35 for serial slaves and 60 for tcp slaves.
  • reconnectAfterMillis: Time after which connection is disconnected and re-established, in milliseconds. Default 0 (closes connection after every transaction).
  • interConnectDelayMillis: Time to wait between consecutive connection attempts (to the same host or ip), in milliseconds. Default 0.
  • connectMaxTries: Maximum tries when establishing connection. Default 3.
  • connectTimeout: Maximum time to wait for connection establishment, in milliseconds. Default is zero which corresponds to infinite/OS default.
  • receiveTimeoutMillis: Maximum time to wait for a single “read operation” (sequence of bytes) before giving up, in milliseconds. Default 1500.
  • flowControlIn: Flow control for the input data. Default none. Valid values: none, xon/xoff in, rts/cts in.
  • flowControlOut: Flow control for the output data. Default none. Valid values: none, xon/xoff out, rts/cts out.

Most important of these is the interTransactionDelayMillis which ensures that Modbus RTU (serial) has enough “silent time” between the transactions, as required by the Modbus/RTU protocol. Furthermore it ensures that modbus slave (applies to both tcp and serial slaves) is not spammed with too many transactions, for example some PLC devices might not be able to handle many requests coming in a short time window.

These new parameters have conservative defaults, meaning that they should work for most users. In some cases when extreme performance is required (e.g. poll period below 10ms), one might want to decrease the delay parameters, especially interTransactionDelayMillis. With some slower devices on might need to increase the values.

With low baud rates and/or long read requests (that is, large length parameter is modbus slave definition), there might be need to increase receiveTimeoutMillis to something larger, e.g. 5000.


  • serial.serialslave1.connection=/dev/ttyS0:38400:8:none:1:rtu:150 connect to serial slave using with interTransactionDelayMillis of 150ms
  • tcp.tcpslave1.connection= connect to tcp slave with no interTransactionDelayMillis

Item Configuration

ModbusBindingProvider provides binding for openHAB Items.

The item configuration has two formats: simple and extended.

Simple format

Simple item configuration format looks like the below:

Switch MySwitch "My Modbus Switch" (ALL) {modbus="slave1:5"}

where slave1 is the name of the slave in openhab configuration, and 5 is the reference to the coil, discrete input, input register or holding register.

The index 5 is expressed relative to the slave start parameter and valueType parameter to determine which bits are used when interpreting the value. See also Comment on addressing and Register interpretation (valuetype) on read & write for more details on this.

Switch MySwitch "My Modbus Switch" (ALL) {modbus="slave1:<6:>7"}

In this case data is read from “index 6”, while data is written to “index 7”.

Extended format

In addition to simple format, a new configuration syntax was introduced in version 1.10.0.

This gives more freedom to the user. Among other things it enables

  • specify transformation to use on read/write
  • mark item as read-only (received openHAB commands are not processed)
  • mark item as write-only (polled data do not change the item state)
  • overriding valueType per-item basis (allows to read many registers at once and then interpret them using different value types)
  • processing only certain commands (on write)
  • processing only certain state updates (on read) based on value

The extended format looks like:

(for read)

<[slaveName:readIndex:trigger=TRIGGER, transformation=TRANSFORMATION, valueType=VALUETYPE]

(for write)

>[slaveName:writeIndex:trigger=TRIGGER, transformation=TRANSFORMATION, valueType=VALUETYPE]

Read and write entries can be combined, and there can be zero or more read/write entries. All the keyword arguments after index are optional. Defaults are such that they correspond to binding behaviour currently. Multiple read/write definitions can be specified with commas (whitespace allowed as well).

Example of having multiple entries:

<[slaveName:0], >[slaveName:0], >[slaveName:1]

The above item config would read from “index 0”, and write to both “index 0” and “index 1”.

Similar to simple configuration syntax, place the configuration string in modbus parameter of the item. For example,

Switch MySwitch "My Modbus Switch" (ALL) {modbus="<[slaveName:0], >[slaveName:0], >[slaveName:1]"}

Explanation of different keyword arguments

  • trigger: a string matching command (write definitions) or state (read definitions). If the command/state does not match the trigger value, the command/state is not processed by this write/read definition.
  • use * to process all (overrides slave’s updateunchangeditems)
  • use CHANGED (case-insensitive) with read entries to handle only changed values (similar to slave setting updateunchangeditems=false).
  • use default (case-insensitive) to handle all or just the changed values, depending on slave updateunchangeditems parameter
  • if omitted, uses default.
  • transformation: transformation to use interpreting the result.
  • use default (case-insensitive) to communicate that no transformation is done and value should be passed as is
  • use SERVICE(ARG) to refer to transformation service.
  • any other value than the above types will be interpreted as static text, in which case the actual content of the command/polled value is ignored.
  • if omitted, uses default.
  • valueType: valueType to use when interpreting the register
  • use default (case-insensitive) to refer to slave definition’s valueType
  • if omitted, uses default. Allows to poll the data only once with a single request, and then interpret pieces of data with the correct value type.

The transformations can convert to string representing command and state types of the item. You can also always use numbers (that is, DecimalType), ON/OFF and OPEN/CLOSED. The transformation can be quoted in case of special characters (e.g. = or ,). One can use java-like escaping inside the quotes for complex transformations such as regular expressions (e.g. \").

Item configuration examples

Single coil/register per item

Switch MySwitch "My Modbus Switch" (ALL) {modbus="slave1:5"}
  • This binds MySwitch to modbus slave defined as “slave1” in modbus.cfg reading/writing to the coil (5 + slave’s start index). The 5 is called item read index.
  • If the slave is read-only, that is the type is input or discrete, the binding ignores any write commands.
  • if the slave1 refers to registers, and after parsing using the registers as rules defined by the valuetype, zero value is considered as OFF, everything else as ON.

Separate index for reading and writing

Switch MySwitch "My Modbus Switch" (ALL) {modbus="slave1:<6:>7"}
  • In this case index 6 is used for reading while and commands are put to index 7.

Index and value types

Number MyCounterH "My Counter high [%d]" (All) {modbus="slave3:0"}

this reads counter 1 high word when valuetype=int8 or uint8

Number MyCounterL "My Counter low [%d]" (All) {modbus="slave3:1"}

this reads counter 1 low word when valuetype=int8 or uint8

32bit floating point value numbers

When using a float32 value you must use [%f] in item description.

Number MyCounter "My Counter [%f]" (All) {modbus="slave5:0"}`

The above reads two registers, starting from index 0 (plus slave offset start).

Read-only items

Number NumberItem "Number [%.1f]" <temperature> {modbus="<[slave1:0]"}

Write-only items

Number NumberItem "Number [%.1f]" <temperature> {modbus=">[slave1:0]"}

Set coil to 1 on any command

(inspired by [Modbus] Coil reset only after item update to ON (OFF excepted) #4745)

Reads from index 0, writes to index 1. All writes (no matter what command) are converted to 1.

Number NumberItem "Number [%.1f]" <temperature> {modbus="<[slave1:0], >[slave1:1:transformation=ON]"}

JS transformation (scaling)

This example multiplies the DecimalType commands by 10 when writing to modbus, and divides the values by 10 when reading from modbus.


Number NumberItem "Number [%.1f]" <temperature> {modbus=">[slave1:0:transformation=JS(multiply10.js)], <[slave1:0:transformation=JS(divide10.js)]"}


// Wrap everything in a function
(function(i) {
    return Math.round(parseFloat(i, 10) * 10);
// input variable contains data passed by openhab


// Wrap everything in a function
(function(i) {
    return parseFloat(i) / 10;
// input variable contains data passed by openhab


(inspired by [modbus] enhance modbus binding for rollershutter items #4654)

This is an example how different Rollershutter commands can be written to MODBUS.

Roller shutter position is read from register 0, UP(=1)/DOWN(=-1) commands are written to register 1, and MOVE(=1)/STOP(=0) commands are written to register 2.


Rollershutter RollershutterItem "Roller shutter position [%.1f]" <temperature> {modbus="<[slave1:0], >[slave1:1:trigger=UP, transformation=1], >[slave1:1:trigger=DOWN, transformation=-1], >[slave1:2:trigger=MOVE, transformation=1], >[slave1:2:trigger=STOP, transformation=0]"}


sitemap demo label="Main Menu" {
    Switch item=RollershutterItem label="Roller shutter [(%d)]" mappings=[UP="up", STOP="X", DOWN="down"]


Modbus functions supported

Supported Modbus object types

Modbus binding allows to connect to multiple Modbus slaves. The binding supports following Modbus object types

  • coils, also known as digital out (DO) (read & write)
  • discrete inputs, also known as digital in (DI) (read)
  • input registers (read)
  • holding registers (read & write)

Binding can be configured to interpret values stored in the 16bit registers in different ways, e.g. as signed or unsigned integer.

For more information on these object types, please consult Modbus wikipedia article.

Read and write functions

Modbus specification has different operations for reading and writing different object types. These types of operations are identified by function code. Some devices support only certain function codes.

For more background information, please consult Modbus wikipedia article.

The binding uses following function codes when communicating with the slaves:

  • read coils: function code (FC) 1 (Read Coils)
  • write coil: FC 5 (Write Single Coil)
  • read discrete inputs: FC 2 (Read Discrete Inputs)
  • read input registers: FC 4 (Read Input Registers)
  • read holding registers: FC 3 (Read Multiple Holding Registers)
  • write holding register: FC 6 (Write Single Holding Register), OR FC 16 (Write Multiple Holding Registers) (see note on writemultipleregisters configuration parameter below)

Comment on addressing

Modbus Wikipedia article summarizes this excellently:

In the traditional standard, [entity] numbers for those entities start with a digit, followed by a number of four digits in range 1–9,999:

  • coils numbers start with a zero and then span from 00001 to 09999
  • discrete input numbers start with a one and then span from 10001 to 19999
  • input register numbers start with a three and then span from 30001 to 39999
  • holding register numbers start with a four and then span from 40001 to 49999

This translates into [entity] addresses between 0 and 9,998 in data frames.

The openHAB modbus binding uses data frame entity addresses when referring to modbus entities. That is, the entity address configured in modbus binding is passed to modbus protocol frame as-is. For example, modbus slave definition with start=3, length=2 and type=holding will read modbus entities with the following numbers 40004 and 40005.

Many modbus binding slaves for single physical slave

One needs to configure as many modbus slaves to openHAB as there are corresponding modbus requests. For example, in order to poll status of coil and holding items from a single [physical] modbus slave, two separate modbus slave definitions need to be configured in the modbus.cfg. For example:



Please note that the binding requires that all slaves connecting to the same serial port share the same connection parameters (e.g. baud-rate, parity, ..). In particular are different parameter settings considered bad practice, because they can confuse the instances (slaves) on the modbus. For additional information, see this discussion in the community forums.

Similarly, one must have identical connection parameters for all tcp slaves connecting to same host+port.

Read and write functions (modbus slave type)

Modbus read functions

  • type=coil uses function 1 “Read Coil Status”
  • type=discrete uses function 2 “Read Input Status” (readonly inputs)
  • type=holding uses function 3, “Read Holding Registers”
  • type=input uses function 4 “Read Input Register” (readonly-registers eG analogue inputs)

Modbus write functions

  • type=coil uses function 5 “Write Single Coil”
  • type=holding uses function 6 “Write Single Register”, or function 16 “Write Multiple registers” when writemultipleregisters is true

See also and wikipedia article.

Register interpretation (valuetype) on read & write

Note that this section applies to register elements only (holding or input type)


When the binding interprets and converts polled input registers (input) or holding registers (holding) to openHAB items, the process goes like this:

    1. register(s) are first parsed to a number (see below for the details, exact logic depends on valuetype)
  • 2a. if the item is Switch or Contact: zero is converted CLOSED / OFF. Other numbers are converted to OPEN / ON.
  • 2b. if the item is Number: the value is used as is
    1. transformation is done to the value, if configured. The transformation output (string) is parsed to state using item’s accepted state types (e.g. number, or CLOSED/OPEN).

Polled registers from the Modbus slave are converted to openHAB state. The exact conversion logic depends on valuetype as described below.

Note that first register refers to register with address start (as defined in the slave definition), second register refers to register with address start + 1 etc. The index refers to item read index, e.g. item Switch MySwitch "My Modbus Switch" (ALL) {modbus="slave1:5"} has 5 as read index.


  • a single bit is read from the registers
  • indices between 0…15 (inclusive) represent bits of the first register
  • indices between 16…31 (inclusive) represent bits of the second register, etc.
  • index 0 refers to the least significant bit of the first register
  • index 1 refers to the second least significant bit of the first register, etc.

(Note that updating a bit in a holding type register will NOT work as expected across Modbus, the whole register gets rewritten. Best to use a read-only mode, such as Contact item. Input type registers are by definition read-only.)


  • a byte (8 bits) from the registers is interpreted as signed integer
  • index 0 refers to low byte of the first register, 1 high byte of first register
  • index 2 refers to low byte of the second register, 3 high byte of second register, etc.
  • it is assumed that each high and low byte is encoded in most significant bit first order


  • same as int8 except values are interpreted as unsigned integers


  • register with index (counting from zero) is interpreted as 16 bit signed integer.
  • it is assumed that each register is encoded in most significant bit first order


  • same as int16 except values are interpreted as unsigned integers


  • registers (2 index) and ( 2 *index + 1) are interpreted as signed 32bit integer.
  • it assumed that the first register contains the most significant 16 bits
  • it is assumed that each register is encoded in most significant bit first order


  • same as int32 except values are interpreted as unsigned integers


  • registers (2 index) and ( 2 *index + 1) are interpreted as signed 32bit floating point number.
  • it assumed that the first register contains the most significant 16 bits
  • it is assumed that each register is encoded in most significant bit first order
Word Swapped valuetypes (New since 1.9.0)

The MODBUS specification defines each 16bit word to be encoded as Big Endian, but there is no specification on the order of those words within 32bit or larger data types. The net result is that when you have a master and slave that operate with the same Endian mode things work fine, but add a device with a different Endian mode and it is very hard to correct. To resolve this the binding supports a second set of valuetypes that have the words swapped.

If you get strange values using the int32, uint32 or float32 valuetypes then just try the int32_swap, uint32_swap or float32_swap valuetype, depending upon what your data type is.


  • registers (2 index) and ( 2 *index + 1) are interpreted as signed 32bit integer.
  • it assumed that the first register contains the least significant 16 bits
  • it is assumed that each register is encoded in most significant bit first order (Big Endian)


  • same as int32_swap except values are interpreted as unsigned integers


  • registers (2 index) and ( 2 *index + 1) are interpreted as signed 32bit floating point number.
  • it assumed that the first register contains the least significant 16 bits
  • it is assumed that each register is encoded in most significant bit first order (Big Endian)
Extra notes
  • valuetypes smaller than one register (less than 16 bits) actually read the whole register, and finally extract single bit from the result.


When the binding processes openHAB command (e.g. sent by sendCommand as explained here), the process goes as follows

  1. it is checked whether the associated item is bound to holding register. If not, command is ignored.
  2. command goes through transformation, if configured. No matter what commands the associated item accepts, the transformation can always output number (DecimalType), OPEN/CLOSED (OpenClosedType) and ON/OFF (OnOffType).
  3. command is converted to 16bit integer (in two’s complement format). See below for details.
  4. the 16bits are written to the register with address start (as defined in the slave definition)

Conversion rules for converting command to 16bit integer

  • UP, ON, OPEN commands that are converter to number 1
  • DOWN, OFF, CLOSED commands are converted to number 0
  • Decimal commands are truncated as 32 bit integer (in 2’s complement representation), and then the least significant 16 bits of this integer are extracted.
  • INCREASE, DECREASE: see below

Other commands are not supported.

Note: The way Decimal commands are handled currently means that it is probably not useful to try to use Decimal commands with non-16bit valuetypes.

Converting INCREASE and DECREASE commands to numbers is more complicated

(After 1.10.0)

  1. Most recently polled state (as it has gone through the read transformations etc.) of this item is acquired
  2. add/subtract 1 from the state. If the state is not a number, the whole command is ignored.

(Before 1.10.0)

  1. Register matching (start + read index) is interpreted as unsigned 16bit integer. Previous polled register value is used
  2. add/subtract 1 from the integer

Note (before 1.10.0): note that INCREASE and DECREASE ignore valuetype when using the previously polled value. Thus, it is not recommended to use INCREASE and DECREASE commands with other than valuetype=uint16

Modbus RTU over TCP

Some devices uses modbus RTU over TCP. This is usually Modbus RTU encapsulation in an ethernet packet. So, those devices does not work with Modbus TCP binding since it is Modbus with a special header. Also Modbus RTU over TCP is not supported by Openhab Modbus Binding. But there is a workaround: you can use a Virtual Serial Port Server, to emulate a COM Port and Bind it with OpenHab unsing Modbus Serial.

Config Examples

Please take a look at Samples-Binding-Config page or examine to the following examples.

  • Minimal construction in modbus.cfg for TCP connections will look like:
# read 10 coils starting from address 0
  • Minimal construction in modbus.cfg for serial connections will look like:
# read 10 coils starting from address 0
  • More complex setup could look like
# Poll values very 300ms = 0.3 seconds

# Connect to modbus slave at, port 502
# use slave id 41 in requests
# read 32 coils (digital outputs) starting from address 0
  • Another example where coils, discrete inputs (discrete) and input registers (input) are polled from modbus tcp slave at

(original example description:) example for an moxa e1214 module in simple io mode 6 output switches starting from modbus address 0 and 6 inputs from modbus address 10000 (the function 2 implizits the modbus 10000 address range) you only read 6 input bits and say start from 0 the moxa manual ist not right clear in this case


# Query coils from

# Query discrete inputs from

# Query input registers from

# Query holding registers from
# Holding registers matching addresses 33 and 34 are read

# Query 2 input registers from 
# Interpret the two registers as single 32bit floating point number

Above we used the same modbus gateway with ip multiple times on different modbus address ranges and modbus functions.


Enable verbose logging

Enable DEBUG or TRACE (even more verbose) logging for the loggers named:

  • net.wimpi.modbus
  • org.openhab.binding.modbus

For developers

Testing serial implementation

You can use test serial slaves without any hardware on linux using these steps:

  1. Set-up virtual null modem emulator using tty0tty
  2. Download diagslave and start modbus serial slave up using this command:
./diagslave -m rtu -a 1 -b 38400 -d 8 -s 1 -p none -4 10 /dev/pts/7
  1. Configure openHAB’s modbus slave to connect to /dev/pts/8:
  1. Modify or to include the unconventional port name by adding the following argument to java:

Naturally this is not the same thing as the real thing but helps to identify simple issues.

Testing TCP implementation

  1. Download diagslave and start modbus tcp server (slave) using this command:
./diagslave -m tcp -a 1 -p 55502
  1. Configure openHAB’s modbus slave to connect to

Writing data

See this community post explaining how pollmb and diagslave can be used to debug modbus communication.


To troubleshoot, you might be asked to update to latest development version. You can find the “snapshot” or development version from Cloudbees CI.

With modbus binding before 1.9.0, it strongly recommended to try out with the latest development version since many bugs were fixed in 1.9.0. Furthermore, error logging is enhanced in this new version.

If the problem persists in the new version, it is recommended to try to isolate to issue using minimal configuration. Easiest would be to have a fresh openHAB installation, and configure it minimally (if possible, single modbus slave in modbus.cfg, single item, no rules etc.). This helps the developers and community to debug the issue.

Problems can be communicated via Please use the search function to find out existing reports of the same issue.

It helps greatly to document the issue in detail (especially how to reproduce the issue), and attach the related verbose logs. Try to keep interaction minimal during this test; for example, if the problem occurs with modbus read alone, do not touch the the switch items in openHAB sitemap (would trigger write).

For attaching the logs to a community post, the service is strongly recommended to keep the thread readable. It is useful to store the logs from openHAB startup, and let it run for a while.

Remember to attach configuration lines from modbus.cfg, item definitions related to modbus binding, and any relevant rules (if any). You can use for this purpose as well.

To summarize, here are the recommended steps in case of errors

  1. Update to latest development version; especially if you are using modbus binding version before 1.9.0
  2. isolate the issue using minimal configuration, and enable verbose logging (see above)
  3. record logs and configuration to