Implementing Modbus TCP with Python modbus_tk: Server/Client Examples, API Notes, and Function Codes
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.
- Parameters:
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