Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Implementing Modbus TCP with Python modbus_tk: Server/Client Examples, API Notes, and Function Codes

Notes Apr 9 45

Environment and Setup

  • Language/runtime: Python 3.x (modbus_tk also supports Python 2.7 if required)
  • Platforms: Windows, Linux, Raspberry Pi
  • Optional tools: any Modbus TCP test client/server for quick validation

Installation

  • Install via pip:
pip install modbus_tk

TCP Server (Slave) example

The program below brings up a Modbus TCP server, creates two slave devices, defines several blocks, and seeds some values. Adjust the bind address and port to match your environment and firewall configuration.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import modbus_tk
import modbus_tk.defines as cst
import modbus_tk.modbus_tcp as modbus_tcp

log = modbus_tk.utils.create_logger(name="server", record_format="%(message)s")

if __name__ == "__main__":
    tcp_srv = modbus_tcp.TcpServer(address="0.0.0.0", port=1502)
    try:
        log.info("Starting Modbus TCP server on 0.0.0.0:1502")
        tcp_srv.start()

        # Slave 1: two holding-register blocks
        s1 = tcp_srv.add_slave(1)
        s1.add_block("hrA", cst.HOLDING_REGISTERS, 0, 4)    # addresses 0..3
        s1.add_block("hrB", cst.HOLDING_REGISTERS, 4, 14)   # addresses 4..17

        # Slave 2: one coil block and one holding-register block
        s2 = tcp_srv.add_slave(2)
        s2.add_block("coX", cst.COILS, 0, 12)               # coils 0..11
        s2.add_block("hrX", cst.HOLDING_REGISTERS, 0, 8)    # addresses 0..7

        # Initialize values
        s1.set_values("hrA", 0, [100, 200, 300, 400])
        s1.set_values("hrB", 4, [7, 9, 11, 13, 15, 17, 19, 21])
        s2.set_values("coX", 0, [1, 1, 1, 0, 0, 1])
        s2.set_values("hrX", 0, list(range(8)))

        log.info("Server is running. Press Ctrl+C to stop.")
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        log.info("Stopping server")
    finally:
        tcp_srv.stop()

Notes:

  • A Modbus TCP "server" in modbus_tk hosts one or more slaves (by slave ID).
  • Blocks partition memory regions. A single Modbus request cannot span two blocks.

TCP Client (Master) example

The client connects to the server and exercises common function codes. The example also demonstrates a failed read that attempts to span two blocks on slave 1.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import modbus_tk
import modbus_tk.defines as cst
import modbus_tk.modbus_tcp as modbus_tcp

log = modbus_tk.utils.create_logger(name="client", record_format="%(message)s")

if __name__ == "__main__":
    try:
        master = modbus_tcp.TcpMaster(host="127.0.0.1", port=1502)
        master.set_timeout(5.0)
        log.info("Connected to 127.0.0.1:1502")

        # Slave 1 holding registers
        hr_0_3 = master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 4)
        log.info("s1 HR[0..3]: %s", hr_0_3)

        hr_4_11 = master.execute(1, cst.READ_HOLDING_REGISTERS, 4, 8)
        log.info("s1 HR[4..11]: %s", hr_4_11)

        # Write and verify HR[0..2]
        master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 0, output_value=[42, 43, 44])
        log.info("s1 HR[0..3] after write: %s", master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 4))

        # Attempt a cross-block read (addresses 2..9) -> should fail
        try:
            master.execute(1, cst.READ_HOLDING_REGISTERS, 2, 8)
        except modbus_tk.modbus.ModbusError as exc:
            log.error("Cross-block read failed: %s (code=%d)", exc, exc.get_exception_code())

        # Slave 2 coils and holding registers
        coils_0_7 = master.execute(2, cst.READ_COILS, 0, 8)
        log.info("s2 CO[0..7]: %s", coils_0_7)

        master.execute(2, cst.WRITE_MULTIPLE_COILS, 0, output_value=[1, 0, 1, 0, 1, 0, 0, 1])
        log.info("s2 CO[0..7] after write: %s", master.execute(2, cst.READ_COILS, 0, 8))

        log.info("s2 HR[0..3]: %s", master.execute(2, cst.READ_HOLDING_REGISTERS, 0, 4))

    except modbus_tk.modbus.ModbusError as err:
        log.error("Modbus error: %s (code=%d)", err, err.get_exception_code())

Key API Behaviors in modbus_tk

  • TcpServer.add_slave(slave_id, usnigned=True, memory=None)

    • Valid slave IDs are 1–255; creating an existing ID raises an error.
    • Returns a Slave object used to define memory blocks.
  • Slave.add_block(name, block_type, starting_address, size)

    • Defines a block of coils, discrete inputs, holding registers, or input registers.
    • Requests must fully reside within a single block; crossing a block boundary triggers an exception.
  • Slave.set_values(block_name, address, values)

    • If values is an int, exactly one item is written.
    • If values is a list/tuple, items are written sequentially starting at the address.
    • The write must fit within the target block’s range.
  • TcpMaster.execute(slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="", expected_length=-1)

    • Parameters:
      • slave: target slave ID (1–255)
      • function_code: one of the Modbus function codes from modbus_tk.defines
      • starting_address: first coil/register address
      • quantity_of_x: number of items to read/write (ignored for some functions)
      • output_value: data to write (int for single, list/tuple for multiple)
      • data_format: optional struct-like format for interpreting raw data
      • expected_length: optional response length hint
    • Returns a tuple containing the response data for read requests.

Example usage patterns:

# Create a slave and blocks
s = tcp_srv.add_slave(10)
s.add_block("coils", cst.COILS, 0, 16)
s.add_block("hrs", cst.HOLDING_REGISTERS, 100, 32)

# Set values (single and multiple)
s.set_values("coils", 0, 1)
s.set_values("hrs", 100, [123, 456, 789])

# Read back values
master.execute(10, cst.READ_COILS, 0, 8)
master.execute(10, cst.READ_HOLDING_REGISTERS, 100, 3)

Function and Exception Codes (modbus_tk.defines)

  • Exceptions

    • ILLEGAL_FUNCTION = 1
    • ILLEGAL_DATA_ADDRESS = 2
    • ILLEGAL_DATA_VALUE = 3
    • SLAVE_DEVICE_FAILURE = 4
    • COMMAND_ACKNOWLEDGE = 5
    • SLAVE_DEVICE_BUSY = 6
    • MEMORY_PARITY_ERROR = 8
  • Function codes

    • READ_COILS = 1
    • READ_DISCRETE_INPUTS = 2
    • READ_HOLDING_REGISTERS = 3
    • READ_INPUT_REGISTERS = 4
    • WRITE_SINGLE_COIL = 5
    • WRITE_SINGLE_REGISTER = 6
    • WRITE_MULTIPLE_COILS = 15
    • WRITE_MULTIPLE_REGISTERS = 16
  • Block types

    • COILS = 1
    • DISCRETE_INPUTS = 2
    • HOLDING_REGISTERS = 3
    • ANALOG_INPUTS = 4

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Spring Boot MyBatis with Two MySQL DataSources Using Druid

Required dependencies application.properties: define two data sources and poooling Java configuration for both data sources MyBatis mappers for each data source Controller endpoints to verify both co...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.