PyModbus
PyModbusDocs

Error Handling

Handle Modbus errors with PyModbus. Exception types, error codes, retry patterns, timeout handling, and connection recovery for production systems.

Error Handling in PyModbus

Modbus communication fails in predictable ways: timeouts, bad addresses, connection drops, and corrupt data. Here's how to handle each.

Exception Types

PyModbus raises specific exceptions for different failure modes.

from pymodbus.exceptions import (
    ModbusException,                  # Base exception
    ModbusIOException,                # I/O errors
    ParameterException,               # Invalid parameters
    NoSuchSlaveException,             # Slave not found
    NotImplementedException,          # Function not supported
    ConnectionException,              # Connection issues
    InvalidMessageReceivedException   # Corrupt message
)

try:
    result = client.read_holding_registers(0, 10)
except ConnectionException as e:
    print(f"Connection error: {e}")
except ModbusIOException as e:
    print(f"I/O error: {e}")
except ModbusException as e:
    print(f"Modbus error: {e}")

Modbus Exception Codes

When a request reaches the device but fails, you get an error response with an exception code.

CodeNameMeaning
0x01Illegal FunctionFunction code not supported
0x02Illegal Data AddressAddress not allowed
0x03Illegal Data ValueValue out of range
0x04Slave Device FailureDevice internal error
0x05AcknowledgeRequest accepted, still processing
0x06Slave Device BusyTry again later
0x08Memory Parity ErrorDevice memory error
0x0AGateway Path UnavailableGateway misconfigured
0x0BGateway Target FailedTarget device not responding

Track Modbus errors in production

TofuPilot records test results from your PyModbus scripts, tracks pass/fail rates, and generates compliance reports. Free to start.

result = client.read_holding_registers(9999, 10)

if result.isError():
    code = getattr(result, 'exception_code', None)
    if code:
        print(f"Exception code {code}")
    else:
        print(f"Error: {result}")

Basic Try/Except

The most common pattern: catch the error, log it, return a fallback.

from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException

def read_temperature(client):
    try:
        result = client.read_holding_registers(100, 1)
        if result.isError():
            print(f"Modbus error: {result}")
            return None
        return result.registers[0] / 10.0
    except ModbusException as e:
        print(f"Exception: {e}")
        return None

client = ModbusTcpClient('192.168.1.100')
client.connect()
temp = read_temperature(client)
client.close()

Retry with Backoff

For transient errors (timeouts, busy devices), retry with increasing delays.

import time
from pymodbus.exceptions import ModbusException

def read_with_retry(client, address, count, max_retries=3, delay=0.5):
    current_delay = delay
    for attempt in range(max_retries):
        try:
            result = client.read_holding_registers(address, count)
            if not result.isError():
                return result.registers
            print(f"Attempt {attempt + 1}/{max_retries}: {result}")
        except ModbusException as e:
            print(f"Attempt {attempt + 1}/{max_retries}: {e}")

        if attempt < max_retries - 1:
            time.sleep(current_delay)
            current_delay *= 2  # Exponential backoff

    return None

Retry Decorator

Wrap any Modbus function with automatic retries.

import time
from functools import wraps
from pymodbus.exceptions import ModbusException

def retry_on_error(max_retries=3, delay=1.0, backoff=2.0):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            current_delay = delay
            for attempt in range(max_retries):
                try:
                    result = func(*args, **kwargs)
                    if hasattr(result, 'isError') and result.isError():
                        raise ModbusException(f"Modbus error: {result}")
                    return result
                except Exception as e:
                    if attempt >= max_retries - 1:
                        raise
                    print(f"Retry {attempt + 1}/{max_retries} after {current_delay}s")
                    time.sleep(current_delay)
                    current_delay *= backoff
        return wrapper
    return decorator

@retry_on_error(max_retries=3, delay=0.5)
def read_temperature(client):
    result = client.read_holding_registers(100, 1)
    return result.registers[0] / 10.0

Connection Recovery

Reconnect automatically when the connection drops.

import time
from pymodbus.client import ModbusTcpClient

def read_with_reconnect(host, address, count, max_retries=3):
    client = ModbusTcpClient(host)

    for attempt in range(max_retries):
        try:
            if not client.connected:
                client.connect()

            result = client.read_holding_registers(address, count)
            if not result.isError():
                return result.registers

            print(f"Modbus error: {result}")

        except Exception as e:
            print(f"Connection error: {e}")
            client.close()

        if attempt < max_retries - 1:
            time.sleep(2 ** attempt)  # 1s, 2s, 4s

    client.close()
    return None

Validation

Check that register values are in a sane range before using them.

from pymodbus.exceptions import ModbusException

def read_validated_temperature(client, min_temp=-50, max_temp=200):
    result = client.read_holding_registers(100, 1)

    if result.isError():
        print(f"Read error: {result}")
        return None

    temperature = result.registers[0] / 10.0

    if not (min_temp <= temperature <= max_temp):
        print(f"Temperature {temperature}C out of range [{min_temp}, {max_temp}]")
        return None

    return temperature

Logging

Use Python's logging module to track errors over time.

import logging
import time
from pymodbus.client import ModbusTcpClient

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('modbus_errors.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('modbus')

def read_with_logging(client, address, count, slave=1):
    start = time.time()
    try:
        result = client.read_holding_registers(address, count, slave)
        elapsed = time.time() - start

        if result.isError():
            logger.error(f"Read error: addr={address}, error={result}, time={elapsed:.3f}s")
            return None

        logger.debug(f"Read ok: addr={address}, time={elapsed:.3f}s")
        return result.registers

    except Exception as e:
        elapsed = time.time() - start
        logger.exception(f"Exception: addr={address}, error={e}, time={elapsed:.3f}s")
        return None

Always implement error handling in production. Unhandled Modbus errors can crash your application or cause it to hang indefinitely.

Error Recovery Strategies

StrategyWhen to use
Retry with backoffTransient errors, timeouts
ReconnectConnection drops
Fallback valuesUse last known good value when reads fail
Graceful degradationContinue with reduced functionality
AlertingNotify operators on persistent errors