Install & Compatibility
Where this runs
tested against v3.15.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.330s · 18.9MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 1.7s · import 0.300s · 19MB
17MB installed
● package 17MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
ModbusTcpClient
✓ from pymodbus.client import ModbusTcpClient
✗ from pymodbus.client.sync import ModbusTcpClient
Pymodbus v3.x is primarily asyncio-based. Synchronous clients (e.g., in `pymodbus.client.sync`) were removed or heavily refactored in major version 3 and should not be used for new development.
StartAsyncTcpServer
✓ from pymodbus.server import StartAsyncTcpServer
✗ from pymodbus.server.sync import ModbusTcpServer
Pymodbus v3.x server implementations are primarily asynchronous. Synchronous servers were removed or heavily refactored. Use `StartAsyncTcpServer` for TCP, `StartAsyncUdpServer` for UDP, or `StartAsyncSerialServer` for serial.
ModbusSlaveContext
✓ from pymodbus.datastore import ModbusSlaveContext, ModbusSequentialDataBlock
Used for defining server data storage. The `ModbusSequentialDataBlock` is a common block type.
This quickstart demonstrates a basic asynchronous Modbus TCP server and client. The server runs on localhost:5020, serving a simple datastore. The client connects, reads initial holding registers, writes new values, and then reads again to verify the write operation, before gracefully shutting down the server.
import asyncio
import logging
from pymodbus.client import ModbusTcpClient
from pymodbus.server import StartAsyncTcpServer
from pymodbus.datastore import ModbusSlaveContext, ModbusSequentialDataBlock
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
async def run_modbus_server():
# Setup a simple Modbus datastore for Slave ID 1
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [17]*10),
co=ModbusSequentialDataBlock(0, [17]*10),
hr=ModbusSequentialDataBlock(0, [17]*10),
ir=ModbusSequentialDataBlock(0, [17]*10)
)
context = ModbusSlaveContext(slaves={0x01: store}, single=False)
server_task = StartAsyncTcpServer(
context=context,
address=("localhost", 5020),
allow_reuse_address=True
)
log.info("Modbus TCP Server starting on localhost:5020")
await server_task # This will block until cancelled
async def run_modbus_client():
await asyncio.sleep(1) # Give server a moment to start
log.info("Modbus TCP Client connecting to localhost:5020")
client = ModbusTcpClient("localhost", 5020)
if await client.connect():
log.info("Client connected successfully.")
# Read holding registers (address 0, count 5, slave ID 1)
result = await client.read_holding_registers(address=0, count=5, slave=1)
if result.is_success():
log.info(f"Read holding registers: {result.registers}")
else:
log.error(f"Failed to read holding registers: {result}")
# Write to holding registers (address 0, values [99, 98, 97], slave ID 1)
write_result = await client.write_registers(address=0, values=[99, 98, 97], slave=1)
if write_result.is_success():
log.info(f"Wrote to holding registers.")
else:
log.error(f"Failed to write holding registers: {write_result}")
# Read again to verify write
result_after_write = await client.read_holding_registers(address=0, count=5, slave=1)
if result_after_write.is_success():
log.info(f"Read holding registers after write: {result_after_write.registers}")
else:
log.error(f"Failed to read holding registers after write: {result_after_write}")
client.close()
log.info("Client disconnected.")
else:
log.error("Client failed to connect.")
async def main():
server_task = asyncio.create_task(run_modbus_server())
try:
await run_modbus_client()
finally:
server_task.cancel() # Signal server to shut down
try:
await server_task
except asyncio.CancelledError:
log.info("Server task cancelled successfully.")
except Exception as e:
log.error(f"Server task ended with unexpected error: {e}")
if __name__ == "__main__":
asyncio.run(main())
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'pymodbus.client.sync'
In pymodbus versions 3.x and later, the synchronous and asynchronous client modules (`client.sync` and `client.async`) were refactored and consolidated.
fixUpdate your import statements to use `from pymodbus.client import ModbusTcpClient` for synchronous TCP communication or `from pymodbus.client import AsyncModbusTcpClient` for asynchronous TCP.
AttributeError: 'ModbusIOException' object has no attribute 'registers'
A Modbus operation failed to receive a valid response from the device (e.g., due to a timeout or connection error), returning a `ModbusIOException` object instead of a successful response object that would contain a `registers` or `bits` attribute. The code then attempts to access this missing attribute.
fixAlways check the `isError()` method of the response object returned by Modbus client operations before attempting to access its data attributes. If `isError()` returns `True`, handle the exception rather than trying to read registers.
Modbus Error: [Input/Output] Unable to decode request
Pymodbus received a response from the Modbus device that it could not parse or decode, often because the packet was malformed, incomplete, or the device does not strictly adhere to the Modbus protocol specification.
fixVerify the device's Modbus implementation and communication settings (e.g., framer type, baud rate, parity). Ensure reliable network communication (no packet loss or fragmentation). Adding small delays between consecutive Modbus requests can sometimes help with devices that are sensitive to polling speed.
'H' format requires 0<= number <= 65535
This error occurs when attempting to write a numerical value larger than a 16-bit unsigned integer (65535) into a single Modbus register using functions like `write_register`. Modbus registers are fundamentally 16-bit.
fixIf you need to write larger values (e.g., 32-bit integers, floats), you must split them into multiple 16-bit registers. Use `pymodbus.payload.BinaryPayloadBuilder` to correctly convert and write these values across multiple registers using `write_registers`.
Upgrade
Version history
3.15.0latest on PyPI · released Aug 13, 2026
Audit
Dependencies
No dependency data recorded yet.