:mod:`umodbus` -- MODBUS protocol implementation ================================================ .. module:: umodbus :synopsis: 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 .. method:: 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) .. method:: 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). .. method:: 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. .. method:: 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. .. method:: 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). .. method:: 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). .. method:: 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). .. method:: 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. .. method:: 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"*. .. method:: 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"*. .. method:: 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``. .. method:: 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`. .. method:: 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`. .. method:: 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 --------- .. data:: 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 ------- .. method:: 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**). * *cls* - function class (ref: `ModbusDevice.TYPES`) * *cb* - the callback function If ``None`` is passed, the corresponding operation will raise the 'Illegal function' MODBUS error. .. method:: ModbusDevice.get_callback(cls) Return the currently installed callback. * *cls* - function class (ref: `ModbusDevice.TYPES`) .. method:: 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) .. method:: 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. .. method:: 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`. .. method:: 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`. .. method:: 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 --------- .. data:: 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 :py:class:`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 :py:class:`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 ------- .. method:: 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 --------- .. function:: 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`) .. function:: 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 .. function:: 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