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.
| Code | Name | Meaning |
|---|---|---|
| 0x01 | Illegal Function | Function code not supported |
| 0x02 | Illegal Data Address | Address not allowed |
| 0x03 | Illegal Data Value | Value out of range |
| 0x04 | Slave Device Failure | Device internal error |
| 0x05 | Acknowledge | Request accepted, still processing |
| 0x06 | Slave Device Busy | Try again later |
| 0x08 | Memory Parity Error | Device memory error |
| 0x0A | Gateway Path Unavailable | Gateway misconfigured |
| 0x0B | Gateway Target Failed | Target 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 NoneRetry 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.0Connection 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 NoneValidation
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 temperatureLogging
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 NoneAlways implement error handling in production. Unhandled Modbus errors can crash your application or cause it to hang indefinitely.
Error Recovery Strategies
| Strategy | When to use |
|---|---|
| Retry with backoff | Transient errors, timeouts |
| Reconnect | Connection drops |
| Fallback values | Use last known good value when reads fail |
| Graceful degradation | Continue with reduced functionality |
| Alerting | Notify operators on persistent errors |