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 1

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...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

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