umodbus – MODBUS protocol implementation

Currently implemented are:

  • MODBUS RTU master

  • MODBUS TCP slave

Example:

import umodbus

# Talk to the MODBUS RTU slave with address 42
# connected to UART bus 1 at 115200 baud
mdev = umodbus.ModbusRtuSlave(1, 115200, 42)

# Read the first holding register
mdev.read_register(0)

# Read the first input register
mdev.read_register(0, fun=4)

class ModbusException

Parent class for MODBUS-related errors

class ModbusSlaveException

MODBUS slave device exception. Details are in class memebers code and message.

class ModbusSlave

Parent class for remote MODBUS slave device implementations. Use an instance of one of derivative classes (or implement your own) in order to talk to a MODBUS slave device (with MicroPython board being the master).

Constructors

class umodbus.ModbusSlave(address, debug=False)

Construct a remote MODBUS slave device instance with the given address. If debug is True, print extended debug information to the REPL

Methods

Each method may raise a ValueError, TypeError, or ModbusSlaveException in case of invalid input parameters or an error thrown by the slave device

ModbusSlave.read_bit(registeraddress, fun=2)

Read one discrete input (if fun == 2), or coil (if fun == 1) in a remote device.

  • registeraddress - starting address.

  • fun - MODBUS function code

This function code is used to read from 1 to 2000 contiguous status of coils in a remote device. Returns an integer value of the coil read (0 or 1)

ModbusSlave.write_bit(registeraddress, value, fun=5)

Write a single output (coil) in a remote device.

  • registeraddress - starting address

  • value - output value (will be converted to 0 or 1 via bool(...))

  • fun - MODBUS function code

This function code is used to write a single output to either ON or OFF in a remote device. Returns the value written (as reported by the slave device).

ModbusSlave.read_bits(registeraddress, count, fun=2)

Read count discrete inputs (if fun == 2), or coils (if fun == 1) in a remote device

  • registeraddress - starting address

  • count - quantity of coils / inputs

  • fun - MODBUS function code

This function code is used to read from 1 to 2000 contiguous status of coils in a remote device. Returns an array of integer values of the coils (inputs) read. Each value is 0 or 1.

ModbusSlave.write_bits(registeraddress, values)

Write a sequence of coils in a remote device (function code 15).

  • registeraddress - starting address

  • values - an array of coil states.

This function code is used to force each coil in a sequence of coils to either ON or OFF in a remote device. Each value in the array will be converted to 0 or 1 via bool(...). Returns True if the operation has succeeded.

ModbusSlave.read_register(registeraddress, fun=3)

Read a single holding (function code 3), or input (function code 4) register in a remote device.

  • registeraddress - starting address

  • fun - MODBUS function code

This function code is used to read the contents of a contiguous block of holding (input) registers in a remote device. Returns the received value (integer).

ModbusSlave.write_register(registeraddress, value)

Write a single holding register in a remote device (function code 6)

  • registeraddress - stating address

  • value - register value (will be converted to int)

This function code is used to write a single holding register in a remote device. Returns the value written (as reported by the slave device).

ModbusSlave.read_registers(regiteraddress, count, fun=3)

Read count holding (or input) registers in a remote device.

  • registeraddress - starting address

  • count - quantity of registers

  • fun - MODBUS function code

This function code is used to read the contents of a contiguous block of holding (input) registers in a remote device. Returns a list of the received values (integers).

ModbusSlave.write_registers(registeraddress, values)

Write a block of contiguous registers in a remote device (function code 16)

  • registeraddress - starting address

  • values - an array of register values (each one will be converted to int)

This function code is used to write a block of contiguous registers (1 to 123 registers) in a remote device. Returns True if the operation has succeeded.

ModbusSlave.read_string(registeraddress, size, fun=3, pad=None)

Read an ASCII string from a remote device. Each 16bit register (holding or input if fun == 3 or fun == 4 respectively) in the remote device is interpreted as two ASCII characters. If pad is not None, the returned string is assumed to be padded with this character, and at most 1 pad byte may be removed from the received data.

  • registeraddress - starting address

  • size - number of ASCII characters to read ((size + 1) // 2 registers will be read).

  • fun - MODBUS function code

  • pad - data padding character

The corresponding fmt value in the ModbusSlave.request call is “string”.

ModbusSlave.write_string(registeraddress, data, pad=' ')

Write an ASCII string to a remote device. Each 16bit holding register in the remote device is interpreted as two ASCII characters. The data transmitted will be padded to an even number of characters using pad character.

  • registeraddress - starting address

  • data - string to be written

  • pad - padding character

The corresponding fmt value in the ModbusSlave.request call is “string”.

ModbusSlave.request(fun, registeraddress, tx=None, count=None, fmt=None, pad=' ')

Generic method used to communicate with the device. All the above methods call this one with the appropriate arguments

  • fun - function code (ref. to the MODBUS application protocol)

  • registeraddress - starting address

  • tx - data to be transmitted accoring to the fmt parameter

  • count - number of entities (coils, registers) to be received from the remote device

  • fmt - data format string (ref. ModbusSlave.SUPPORTED_FUNCTIONS)

  • pad - string padding character

Refer to the MODBUS application protocol for the list of function codes and their meaning, and ModbusSlave.SUPPORTED_FUNCTIONS for a list of supported function codes and accepted data types.

The value returned depends on the function code and the fmt paraterer. May be a value, a list of values, a string, or True.

ModbusSlave._compose(fun, registeraddress, tx, count, fmt, pad=' ')

Implement this method in a derivative class. Must return an APDU to be sent to a remote device

  • fun - function code

  • registeraddress - starting address

  • tx - data to be transmitted (may be None)

  • count - number of entities to be received (may be 0 or None)

  • fmt - data format string

  • pad - string padding character.

Most of the work is done by the umodbus.modbus_pdu_compose function. One just has to call it with the appropriate arguments.

The default implementation raises a RuntimeError.

ModbusSlave._rxtx(adpu, fun, count, fmt)

Implement this method in a derivative class. This function must do the low-level work of sending the APDU to the remote device via the communication channel and reading the remote device response. Raise an error is the data could not be written of the remote device did not repond. Return the data received from the remote device (as a bytes object)

  • apdu - the data to be sent (as returned by the ModbusSlave._compose method).

  • fun - function code

  • count - number of entities to be received (may be 0 or None)

  • fmt - data format string

The default implementation raises a RuntimeError.

ModbusSlave._process(apdu, fun, registeraddress, count, fmt, pad=None)

Implement this method in a derivative class. This function must process the APDU received from the remote device and return the recived reply according to the fmt parameter.

  • apdu - the data received from the remote device

  • fun - function code

  • registeraddress - starting address

  • fmt - data format string

  • pad - string padding character.

Most of the work is done by the umodbus.modbus_pdu_parse function. One just has to call it with the appropriate arguments.

The default implementation raises a RuntimeError.

Constants

ModbusSlave.SUPPORTED_FUNCTIONS

A dictionary mapping modbus function numbers (int) to payload types (string):

{
    1: 'bits'
    2: 'bits',
    3: 'registers',
    4: 'registers',
    5: 'bit',
    6: 'register',
    15: 'bits',
    16: 'registers'
}

class ModbusDevice

Parent class for MODBUS slave device implementations Use an instance of one of derivative classes (or implement your own) in order to become a MODBUS slave device.

Constructors

class umodbus.ModbusDevice(address, debug=False)

Construct a MODBUS slave device instance with the given address. If debug is True, print extended debug information to the REPL

Methods

ModbusDevice.set_callback(cls, cb):

The cb function will be called when a remote master requests a read or write operation.

Read operation:

data = cb(registeraddress)

Write opetation:

cb(registeraddress, value)

The callback must not be a coroutine and for the read operation must return the requested data or raise an exception (preferably a ModbusSlaveException with an appropriate code), for the write operation must accept a register address and a value and raise an exception if the operation cannot be completed. Values are passed as is (ex. coil values are 0x0000 and 0xFF00).

If None is passed, the corresponding operation will raise the ‘Illegal function’ MODBUS error.

ModbusDevice.get_callback(cls)

Return the currently installed callback.

ModbusDevice.process(pdu)

Processes the request pdu (w/o the TCP header), calls the appropriate callback (if set), and returns the reply PDU.

  • pdu - the request PDU (bytes object)

ModbusDevice.runner()

The default implementation of an asyncronous task. Polls for incoming PDU’s, processes the received data and sends the reply

This is a coroutine.

ModbusDevice.poll()

Implement this method in a derivative class. This function must poll the underlying media for incoming data and return the received bytes. The output of this function will be fed to the ModbusDevice._parse method by the default ModbusDevice.runner function.

This is a coroutine. The default implementation raises a RuntimeError.

ModbusDevice._parse(apdu)

Implement this method in a derivative class. This method must process the incoming data (full APDU with all the headers and trailers), pass the stripped PDU to the _process() function and return the reply APDU (with all the headers and trailers attached).

This is not a coroutine. The default implementation raises a RuntimeError.

ModbusDevice._write(apdu)

Implement this method in a derivative class. This function must write the apdu to the underlying media

This is a coroutine. The default implementation does nothing.

Constants

ModbusDevice.TYPES

A set of callback types:

  • “coil” (called for functions 0x01, 0x05, 0x0F)

  • “discrete” (called for function 0x02)

  • “input” (called for function 0x04)

  • “holding” (called for functions 0x03, 0x06, 0x10)

class ModbusRtuSlave

Implements MODBUS RTU protocol as a master, inherits ModbusSlave class.

Constructors

class umodbus.ModbusRtuSlave(port, baud, address, debug=False)

Port may be an instance of pyb.UART or a number. In the letter case, the UART(port) instance will be created internally.

  • port - UART handle or the UART port number.

  • baud - baudrate. If port is a handle, pass the correct baudrate anyway - it is used to calculate the line silence time.

class ModbusRtuDevice

Implements MODBUS RTU protocol as a slave, inherits ModbusDevice.

Constructors

class umodbus.ModbusRtuDevice(port, baud, address, debug=False)

Port may be an instance of pyb.UART or a number. In the letter case, the UART(port) instance will be created internally.

  • port - UART handle or the UART port number.

  • baud - baudrate. If port is a handle, pass the correct baudrate anyway - it is used to calculate the line silence time.

class ModbusTcpSlave

Implements MODBUS TCP protocol as a master, inherits ModbusSlave

Constructors

class umodbus.ModbusTcpSlave(ip, address, port=502, debug=False)

Create a MODBUS master that will talk to a slave device at ip:port with the MODBUS id address.

Note: the constructor will not initiate a connection.

  • ip - IP address of the slave device

  • address - address (1-247) of the slave device

  • port - TCP port the slave device is listening at (defaults to 502)

Methods

ModbusTcpSlave.connect()

Connect to the remote device.

Returns True if the connection succeeds.

class ModbusTcpDevice

Implements MODBUS TCP protocol as a slave, inherits ModbusDevice.

Constructors

class umodbus.ModbusTcpDevice(address, ip='0.0.0.0', port=502, debug=False)

Create a MODBUS slave device that will listen at ip:port and have a MODBUS id address.

  • address - address (1-247) of the device

  • ip - IP address of the network interface

  • port - TCP port the device will be listening at (defaults to 502)

Functions

umodbus.modbus_crc(bytestring)

Calculates the CRC value accoring to the MODBUS RTU protocol specification and returns it as a bytes value.

  • bytestring - CRC source data (bytes)

umodbus.modbus_pdu_compose(fun, registeraddress, data, count, fmt, pad=' ')

Compose the MODBUS PDU.

  • fun - function code

  • registeraddress - starting address

  • data - data to be send to the remote device

  • count - number of entities (coils or registers) to be read from the remote device

  • fmt - format of the data

  • pad - string padding character

umodbus.modbus_pdu_parse(pdu, fun, registeraddress, count, fmt, pad=' ')

Parse the MODBUS PDU received from the remote device

  • pdu - PDU (bytes)

  • fun - expected function code

  • count - expected number of entities received

  • fmt - format of the data received

  • pad - string padding character