0
# Constants and Error Handling
1
2
Protocol constants, function codes, exception codes, and error handling utilities for robust Modbus communication. These constants define the Modbus protocol specifications and provide comprehensive error reporting capabilities.
3
4
## Capabilities
5
6
### Package Constants
7
8
Basic package information and protocol defaults.
9
10
```python { .api }
11
VERSION = '0.3.0' # Package version string
12
MODBUS_PORT = 502 # Default Modbus TCP port
13
MAX_PDU_SIZE = 253 # Maximum Protocol Data Unit size
14
```
15
16
### Function Codes
17
18
Standard Modbus function codes supported by the library.
19
20
```python { .api }
21
# Read functions
22
READ_COILS = 0x01 # Read coils (outputs)
23
READ_DISCRETE_INPUTS = 0x02 # Read discrete inputs
24
READ_HOLDING_REGISTERS = 0x03 # Read holding registers
25
READ_INPUT_REGISTERS = 0x04 # Read input registers
26
27
# Write functions
28
WRITE_SINGLE_COIL = 0x05 # Write single coil
29
WRITE_SINGLE_REGISTER = 0x06 # Write single register
30
WRITE_MULTIPLE_COILS = 0x0F # Write multiple coils
31
WRITE_MULTIPLE_REGISTERS = 0x10 # Write multiple registers
32
33
# Advanced functions
34
WRITE_READ_MULTIPLE_REGISTERS = 0x17 # Write/read multiple registers
35
ENCAPSULATED_INTERFACE_TRANSPORT = 0x2B # Encapsulated interface transport
36
37
# Supported function codes tuple
38
SUPPORTED_FUNCTION_CODES = (
39
READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS,
40
WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS,
41
WRITE_READ_MULTIPLE_REGISTERS, ENCAPSULATED_INTERFACE_TRANSPORT
42
)
43
```
44
45
### MEI Types
46
47
Modbus Encapsulated Interface (MEI) type definitions.
48
49
```python { .api }
50
MEI_TYPE_READ_DEVICE_ID = 0x0E # MEI type for read device identification
51
```
52
53
### Modbus Exception Codes
54
55
Standard Modbus exception codes returned by servers when errors occur.
56
57
```python { .api }
58
EXP_NONE = 0x00 # No exception
59
EXP_ILLEGAL_FUNCTION = 0x01 # Illegal function code
60
EXP_DATA_ADDRESS = 0x02 # Illegal data address
61
EXP_DATA_VALUE = 0x03 # Illegal data value
62
EXP_SLAVE_DEVICE_FAILURE = 0x04 # Slave device failure
63
EXP_ACKNOWLEDGE = 0x05 # Acknowledge
64
EXP_SLAVE_DEVICE_BUSY = 0x06 # Slave device busy
65
EXP_NEGATIVE_ACKNOWLEDGE = 0x07 # Negative acknowledge
66
EXP_MEMORY_PARITY_ERROR = 0x08 # Memory parity error
67
EXP_GATEWAY_PATH_UNAVAILABLE = 0x0A # Gateway path unavailable
68
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B # Gateway target device failed to respond
69
```
70
71
### Exception Text Mappings
72
73
Human-readable descriptions for exception codes.
74
75
```python { .api }
76
# Short exception descriptions
77
EXP_TXT = {
78
EXP_NONE: 'no exception',
79
EXP_ILLEGAL_FUNCTION: 'illegal function',
80
EXP_DATA_ADDRESS: 'illegal data address',
81
EXP_DATA_VALUE: 'illegal data value',
82
EXP_SLAVE_DEVICE_FAILURE: 'slave device failure',
83
EXP_ACKNOWLEDGE: 'acknowledge',
84
EXP_SLAVE_DEVICE_BUSY: 'slave device busy',
85
EXP_NEGATIVE_ACKNOWLEDGE: 'negative acknowledge',
86
EXP_MEMORY_PARITY_ERROR: 'memory parity error',
87
EXP_GATEWAY_PATH_UNAVAILABLE: 'gateway path unavailable',
88
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'gateway target device failed to respond'
89
}
90
91
# Detailed exception descriptions
92
EXP_DETAILS = {
93
EXP_NONE: 'The last request produced no exceptions.',
94
EXP_ILLEGAL_FUNCTION: 'Function code received in the query is not recognized or allowed by slave.',
95
EXP_DATA_ADDRESS: 'Data address of some or all the required entities are not allowed or do not exist in slave.',
96
EXP_DATA_VALUE: 'Value is not accepted by slave.',
97
EXP_SLAVE_DEVICE_FAILURE: 'Unrecoverable error occurred while slave was attempting to perform requested action.',
98
EXP_ACKNOWLEDGE: 'Slave has accepted request and is processing it, but a long duration of time is required. This response is returned to prevent a timeout error from occurring in the master. Master can next issue a Poll Program Complete message to determine whether processing is completed.',
99
EXP_SLAVE_DEVICE_BUSY: 'Slave is engaged in processing a long-duration command. Master should retry later.',
100
EXP_NEGATIVE_ACKNOWLEDGE: 'Slave cannot perform the programming functions. Master should request diagnostic or error information from slave.',
101
EXP_MEMORY_PARITY_ERROR: 'Slave detected a parity error in memory. Master can retry the request, but service may be required on the slave device.',
102
EXP_GATEWAY_PATH_UNAVAILABLE: 'Specialized for Modbus gateways, this indicates a misconfiguration on gateway.',
103
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'Specialized for Modbus gateways, sent when slave fails to respond.'
104
}
105
```
106
107
### Module Error Codes
108
109
Internal library error codes for network and communication issues.
110
111
```python { .api }
112
MB_NO_ERR = 0 # No error
113
MB_RESOLVE_ERR = 1 # Name resolve error
114
MB_CONNECT_ERR = 2 # Connect error
115
MB_SEND_ERR = 3 # Socket send error
116
MB_RECV_ERR = 4 # Socket receive error
117
MB_TIMEOUT_ERR = 5 # Receive timeout error
118
MB_FRAME_ERR = 6 # Frame format error
119
MB_EXCEPT_ERR = 7 # Modbus exception error
120
MB_CRC_ERR = 8 # Bad CRC on receive frame
121
MB_SOCK_CLOSE_ERR = 9 # Socket is closed error
122
```
123
124
### Module Error Text Mapping
125
126
Human-readable descriptions for module error codes.
127
128
```python { .api }
129
MB_ERR_TXT = {
130
MB_NO_ERR: 'no error',
131
MB_RESOLVE_ERR: 'name resolve error',
132
MB_CONNECT_ERR: 'connect error',
133
MB_SEND_ERR: 'socket send error',
134
MB_RECV_ERR: 'socket recv error',
135
MB_TIMEOUT_ERR: 'recv timeout occur',
136
MB_FRAME_ERR: 'frame format error',
137
MB_EXCEPT_ERR: 'modbus exception',
138
MB_CRC_ERR: 'bad CRC on receive frame',
139
MB_SOCK_CLOSE_ERR: 'socket is closed'
140
}
141
```
142
143
## Usage Examples
144
145
### Using Function Codes
146
147
```python
148
from pyModbusTCP.client import ModbusClient
149
from pyModbusTCP.constants import READ_HOLDING_REGISTERS, WRITE_MULTIPLE_REGISTERS
150
151
client = ModbusClient(host="192.168.1.100", auto_open=True)
152
153
# Function codes are used internally, but you can reference them
154
print(f"Reading using function code: {READ_HOLDING_REGISTERS}")
155
registers = client.read_holding_registers(0, 10)
156
157
print(f"Writing using function code: {WRITE_MULTIPLE_REGISTERS}")
158
success = client.write_multiple_registers(0, [100, 200, 300])
159
```
160
161
### Error Handling with Constants
162
163
```python
164
from pyModbusTCP.client import ModbusClient
165
from pyModbusTCP.constants import (
166
MB_NO_ERR, MB_CONNECT_ERR, MB_TIMEOUT_ERR, MB_ERR_TXT,
167
EXP_NONE, EXP_DATA_ADDRESS, EXP_TXT, EXP_DETAILS
168
)
169
170
client = ModbusClient(host="192.168.1.100", auto_open=True)
171
172
# Attempt to read registers
173
registers = client.read_holding_registers(0, 10)
174
175
if registers is None:
176
# Check for network/connection errors
177
error_code = client.last_error
178
if error_code != MB_NO_ERR:
179
print(f"Network error: {MB_ERR_TXT.get(error_code, 'unknown error')}")
180
181
if error_code == MB_CONNECT_ERR:
182
print("Check if server is running and network is accessible")
183
elif error_code == MB_TIMEOUT_ERR:
184
print("Server not responding, consider increasing timeout")
185
186
# Check for Modbus exceptions
187
except_code = client.last_except
188
if except_code != EXP_NONE:
189
print(f"Modbus exception: {EXP_TXT.get(except_code, 'unknown exception')}")
190
print(f"Details: {EXP_DETAILS.get(except_code, 'No details available')}")
191
192
if except_code == EXP_DATA_ADDRESS:
193
print("The requested register address may not exist on the server")
194
else:
195
print(f"Successfully read registers: {registers}")
196
```
197
198
### Server Exception Handling
199
200
```python
201
from pyModbusTCP.server import ModbusServer, DataHandler
202
from pyModbusTCP.constants import (
203
EXP_DATA_ADDRESS, EXP_DATA_VALUE, EXP_ILLEGAL_FUNCTION
204
)
205
206
class ValidatingDataHandler(DataHandler):
207
def read_h_regs(self, address, count, srv_info):
208
# Validate address range
209
if address >= 1000:
210
return self.Return(exp_code=EXP_DATA_ADDRESS)
211
212
# Validate count
213
if count > 100:
214
return self.Return(exp_code=EXP_DATA_VALUE)
215
216
return super().read_h_regs(address, count, srv_info)
217
218
def write_h_regs(self, address, words_l, srv_info):
219
# Validate write address
220
if address < 100: # Read-only area
221
return self.Return(exp_code=EXP_DATA_ADDRESS)
222
223
# Validate data values
224
if any(w > 32767 for w in words_l): # Signed 16-bit limit
225
return self.Return(exp_code=EXP_DATA_VALUE)
226
227
return super().write_h_regs(address, words_l, srv_info)
228
229
# Use validating handler
230
handler = ValidatingDataHandler()
231
server = ModbusServer(data_hdl=handler)
232
server.start()
233
```
234
235
### Function Code Validation
236
237
```python
238
from pyModbusTCP.constants import SUPPORTED_FUNCTION_CODES
239
240
def is_supported_function(func_code):
241
"""Check if a function code is supported."""
242
return func_code in SUPPORTED_FUNCTION_CODES
243
244
# Check function code support
245
test_functions = [0x01, 0x03, 0x10, 0x2B, 0xFF]
246
for func in test_functions:
247
supported = is_supported_function(func)
248
print(f"Function code 0x{func:02X}: {'supported' if supported else 'not supported'}")
249
```
250
251
### Comprehensive Error Reporting
252
253
```python
254
from pyModbusTCP.client import ModbusClient
255
from pyModbusTCP.constants import *
256
257
def detailed_error_report(client):
258
"""Generate detailed error report for a client."""
259
report = []
260
261
# Network error information
262
error_code = client.last_error
263
if error_code != MB_NO_ERR:
264
error_text = MB_ERR_TXT.get(error_code, f'Unknown error code: {error_code}')
265
report.append(f"Network Error ({error_code}): {error_text}")
266
267
# Modbus exception information
268
except_code = client.last_except
269
if except_code != EXP_NONE:
270
except_text = EXP_TXT.get(except_code, f'Unknown exception code: {except_code}')
271
except_details = EXP_DETAILS.get(except_code, 'No additional details available')
272
report.append(f"Modbus Exception ({except_code}): {except_text}")
273
report.append(f"Details: {except_details}")
274
275
return report if report else ["No errors detected"]
276
277
# Example usage
278
client = ModbusClient(host="192.168.1.100", timeout=5.0, auto_open=True)
279
280
# Attempt operation that might fail
281
result = client.read_holding_registers(9999, 100) # Likely to cause address error
282
283
if result is None:
284
error_info = detailed_error_report(client)
285
print("Error Report:")
286
for line in error_info:
287
print(f" {line}")
288
```
289
290
### Protocol Configuration
291
292
```python
293
from pyModbusTCP.server import ModbusServer
294
from pyModbusTCP.constants import MODBUS_PORT, MAX_PDU_SIZE
295
296
# Use standard constants for configuration
297
server = ModbusServer(
298
host="0.0.0.0",
299
port=MODBUS_PORT, # Standard Modbus TCP port
300
no_block=False
301
)
302
303
print(f"Server configured on port {MODBUS_PORT}")
304
print(f"Maximum PDU size: {MAX_PDU_SIZE} bytes")
305
server.start()
306
```
307
308
## Error Handling Best Practices
309
310
### Client-Side Error Handling
311
312
```python
313
from pyModbusTCP.client import ModbusClient
314
from pyModbusTCP.constants import *
315
316
def robust_read_registers(client, address, count, max_retries=3):
317
"""Robust register reading with retry logic."""
318
for attempt in range(max_retries):
319
result = client.read_holding_registers(address, count)
320
321
if result is not None:
322
return result
323
324
error_code = client.last_error
325
except_code = client.last_except
326
327
if error_code == MB_TIMEOUT_ERR:
328
print(f"Timeout on attempt {attempt + 1}, retrying...")
329
continue
330
elif except_code == EXP_SLAVE_DEVICE_BUSY:
331
print(f"Device busy on attempt {attempt + 1}, retrying...")
332
continue
333
else:
334
# Non-recoverable error
335
break
336
337
return None
338
339
# Usage
340
client = ModbusClient(host="192.168.1.100", auto_open=True)
341
registers = robust_read_registers(client, 0, 10)
342
if registers:
343
print(f"Successfully read: {registers}")
344
else:
345
print("Failed to read registers after retries")
346
```
347
348
### Server-Side Validation
349
350
```python
351
from pyModbusTCP.server import ModbusServer, DataHandler
352
from pyModbusTCP.constants import *
353
354
class RobustDataHandler(DataHandler):
355
def __init__(self, data_bank=None):
356
super().__init__(data_bank)
357
self.valid_read_ranges = [(0, 999), (2000, 2999)] # Valid address ranges
358
self.valid_write_ranges = [(100, 899), (2100, 2899)]
359
360
def _validate_read_address(self, address, count):
361
"""Validate if address range is readable."""
362
end_address = address + count - 1
363
for start, end in self.valid_read_ranges:
364
if start <= address <= end and start <= end_address <= end:
365
return True
366
return False
367
368
def _validate_write_address(self, address, count):
369
"""Validate if address range is writable."""
370
end_address = address + count - 1
371
for start, end in self.valid_write_ranges:
372
if start <= address <= end and start <= end_address <= end:
373
return True
374
return False
375
376
def read_h_regs(self, address, count, srv_info):
377
if not self._validate_read_address(address, count):
378
return self.Return(exp_code=EXP_DATA_ADDRESS)
379
380
if count > 125: # Modbus limit
381
return self.Return(exp_code=EXP_DATA_VALUE)
382
383
return super().read_h_regs(address, count, srv_info)
384
385
def write_h_regs(self, address, words_l, srv_info):
386
if not self._validate_write_address(address, len(words_l)):
387
return self.Return(exp_code=EXP_DATA_ADDRESS)
388
389
# Validate data values
390
for word in words_l:
391
if not (0 <= word <= 65535):
392
return self.Return(exp_code=EXP_DATA_VALUE)
393
394
return super().write_h_regs(address, words_l, srv_info)
395
396
# Use robust handler
397
handler = RobustDataHandler()
398
server = ModbusServer(data_hdl=handler)
399
server.start()
400
```