0
# Server Operations
1
2
TCP server functionality for creating Modbus servers that can handle client requests. The server components include the main ModbusServer class, data storage management through DataBank, request handling via DataHandler, and device identification configuration.
3
4
## Capabilities
5
6
### ModbusServer Class
7
8
Main server class that creates a multi-threaded TCP server to handle Modbus client connections.
9
10
```python { .api }
11
class ModbusServer:
12
"""
13
Modbus TCP server with multi-threaded client handling.
14
15
Parameters:
16
host (str): Server bind address (default: "localhost")
17
port (int): TCP port number (default: 502)
18
no_block (bool): Non-blocking server mode (default: False)
19
ipv6 (bool): Enable IPv6 support (default: False)
20
data_bank (DataBank): Data storage instance (default: new DataBank())
21
data_hdl (DataHandler): Request handler instance (default: new DataHandler())
22
ext_engine (object): External engine instance (default: None)
23
device_id (DeviceIdentification): Device identification info (default: None)
24
"""
25
def __init__(self, host="localhost", port=502, no_block=False, ipv6=False, data_bank=None, data_hdl=None, ext_engine=None, device_id=None):
26
"""Initialize Modbus TCP server."""
27
28
def start(self):
29
"""
30
Start the server.
31
32
Returns:
33
bool: True if server started successfully, False otherwise
34
"""
35
36
def stop(self):
37
"""Stop the server."""
38
39
def is_run(self):
40
"""
41
Check if server is running.
42
43
Returns:
44
bool: True if server is running, False otherwise
45
"""
46
```
47
48
#### Properties
49
50
```python { .api }
51
@property
52
def host(self):
53
"""str: Server bind address."""
54
55
@property
56
def port(self):
57
"""int: TCP port number."""
58
59
@property
60
def data_bank(self):
61
"""DataBank: Data storage instance."""
62
63
@property
64
def data_hdl(self):
65
"""DataHandler: Request handler instance."""
66
67
@property
68
def device_identification(self):
69
"""DeviceIdentification: Device identification instance (read-write)."""
70
```
71
72
### DataBank Class
73
74
Default data storage implementation providing in-memory storage for all Modbus data types.
75
76
```python { .api }
77
class DataBank:
78
"""
79
Default data bank for storing coils, discrete inputs, holding registers, and input registers.
80
81
Parameters:
82
coils_size (int): Number of coils (default: 0x10000)
83
coils_default_value (bool): Default coil value (default: False)
84
d_inputs_size (int): Number of discrete inputs (default: 0x10000)
85
d_inputs_default_value (bool): Default discrete input value (default: False)
86
h_regs_size (int): Number of holding registers (default: 0x10000)
87
h_regs_default_value (int): Default holding register value (default: 0)
88
i_regs_size (int): Number of input registers (default: 0x10000)
89
i_regs_default_value (int): Default input register value (default: 0)
90
virtual_mode (bool): Enable virtual mode for external data management (default: False)
91
"""
92
def __init__(self, coils_size=0x10000, coils_default_value=False,
93
d_inputs_size=0x10000, d_inputs_default_value=False,
94
h_regs_size=0x10000, h_regs_default_value=0,
95
i_regs_size=0x10000, i_regs_default_value=0,
96
virtual_mode=False):
97
"""Initialize data bank with specified sizes and default values."""
98
```
99
100
#### Coil Operations
101
102
```python { .api }
103
def get_coils(self, address, number=1, srv_info=None):
104
"""
105
Get coils values.
106
107
Parameters:
108
address (int): Starting coil address (0-65535)
109
number (int): Number of coils to read (default: 1)
110
srv_info (dict): Server information (optional)
111
112
Returns:
113
list[bool] or None: List of coil values, None on error
114
"""
115
116
def set_coils(self, address, bit_list, srv_info=None):
117
"""
118
Set coils values.
119
120
Parameters:
121
address (int): Starting coil address (0-65535)
122
bit_list (list[bool]): List of coil values to set
123
srv_info (dict): Server information (optional)
124
125
Returns:
126
bool: True on success, False on error
127
"""
128
```
129
130
#### Discrete Input Operations
131
132
```python { .api }
133
def get_discrete_inputs(self, address, number=1, srv_info=None):
134
"""
135
Get discrete inputs values.
136
137
Parameters:
138
address (int): Starting input address (0-65535)
139
number (int): Number of inputs to read (default: 1)
140
srv_info (dict): Server information (optional)
141
142
Returns:
143
list[bool] or None: List of input values, None on error
144
"""
145
146
def set_discrete_inputs(self, address, bit_list):
147
"""
148
Set discrete inputs values.
149
150
Parameters:
151
address (int): Starting input address (0-65535)
152
bit_list (list[bool]): List of input values to set
153
154
Returns:
155
bool: True on success, False on error
156
"""
157
```
158
159
#### Holding Register Operations
160
161
```python { .api }
162
def get_holding_registers(self, address, number=1, srv_info=None):
163
"""
164
Get holding registers values.
165
166
Parameters:
167
address (int): Starting register address (0-65535)
168
number (int): Number of registers to read (default: 1)
169
srv_info (dict): Server information (optional)
170
171
Returns:
172
list[int] or None: List of register values (0-65535), None on error
173
"""
174
175
def set_holding_registers(self, address, word_list, srv_info=None):
176
"""
177
Set holding registers values.
178
179
Parameters:
180
address (int): Starting register address (0-65535)
181
word_list (list[int]): List of register values to set (0-65535)
182
srv_info (dict): Server information (optional)
183
184
Returns:
185
bool: True on success, False on error
186
"""
187
```
188
189
#### Input Register Operations
190
191
```python { .api }
192
def get_input_registers(self, address, number=1, srv_info=None):
193
"""
194
Get input registers values.
195
196
Parameters:
197
address (int): Starting register address (0-65535)
198
number (int): Number of registers to read (default: 1)
199
srv_info (dict): Server information (optional)
200
201
Returns:
202
list[int] or None: List of register values (0-65535), None on error
203
"""
204
205
def set_input_registers(self, address, word_list):
206
"""
207
Set input registers values.
208
209
Parameters:
210
address (int): Starting register address (0-65535)
211
word_list (list[int]): List of register values to set (0-65535)
212
213
Returns:
214
bool: True on success, False on error
215
"""
216
```
217
218
#### Change Callbacks
219
220
```python { .api }
221
def on_coils_change(self, address, from_value, to_value, srv_info):
222
"""
223
Callback called when coils are modified (override in subclass).
224
225
Parameters:
226
address (int): Starting address that changed
227
from_value (list[bool]): Previous values
228
to_value (list[bool]): New values
229
srv_info (dict): Server information
230
"""
231
232
def on_holding_registers_change(self, address, from_value, to_value, srv_info):
233
"""
234
Callback called when holding registers are modified (override in subclass).
235
236
Parameters:
237
address (int): Starting address that changed
238
from_value (list[int]): Previous values
239
to_value (list[int]): New values
240
srv_info (dict): Server information
241
"""
242
```
243
244
#### Static Methods (Override Points)
245
246
```python { .api }
247
@staticmethod
248
def get_bits(*_args, **_kwargs):
249
"""Override this method for custom bit access logic."""
250
251
@staticmethod
252
def set_bits(*_args, **_kwargs):
253
"""Override this method for custom bit write logic."""
254
255
@staticmethod
256
def get_words(*_args, **_kwargs):
257
"""Override this method for custom word access logic."""
258
259
@staticmethod
260
def set_words(*_args, **_kwargs):
261
"""Override this method for custom word write logic."""
262
```
263
264
### DataHandler Class
265
266
Request handler that processes Modbus function codes and interacts with the data bank.
267
268
```python { .api }
269
class DataHandler:
270
"""
271
Modbus request handler for processing function codes.
272
273
Parameters:
274
data_bank (DataBank): Data storage instance (default: new DataBank())
275
"""
276
def __init__(self, data_bank=None):
277
"""Initialize request handler with data bank."""
278
```
279
280
#### Request Processing Methods
281
282
```python { .api }
283
def read_coils(self, address, count, srv_info):
284
"""
285
Handle read coils request (function code 1).
286
287
Parameters:
288
address (int): Starting coil address
289
count (int): Number of coils to read
290
srv_info (dict): Server information
291
292
Returns:
293
DataHandler.Return: Response object with success/error status and data
294
"""
295
296
def write_coils(self, address, bits_l, srv_info):
297
"""
298
Handle write coils request (function code 15).
299
300
Parameters:
301
address (int): Starting coil address
302
bits_l (list[bool]): List of coil values to write
303
srv_info (dict): Server information
304
305
Returns:
306
DataHandler.Return: Response object with success/error status
307
"""
308
309
def read_d_inputs(self, address, count, srv_info):
310
"""
311
Handle read discrete inputs request (function code 2).
312
313
Parameters:
314
address (int): Starting input address
315
count (int): Number of inputs to read
316
srv_info (dict): Server information
317
318
Returns:
319
DataHandler.Return: Response object with success/error status and data
320
"""
321
322
def read_h_regs(self, address, count, srv_info):
323
"""
324
Handle read holding registers request (function code 3).
325
326
Parameters:
327
address (int): Starting register address
328
count (int): Number of registers to read
329
srv_info (dict): Server information
330
331
Returns:
332
DataHandler.Return: Response object with success/error status and data
333
"""
334
335
def write_h_regs(self, address, words_l, srv_info):
336
"""
337
Handle write holding registers request (function code 16).
338
339
Parameters:
340
address (int): Starting register address
341
words_l (list[int]): List of register values to write
342
srv_info (dict): Server information
343
344
Returns:
345
DataHandler.Return: Response object with success/error status
346
"""
347
348
def read_i_regs(self, address, count, srv_info):
349
"""
350
Handle read input registers request (function code 4).
351
352
Parameters:
353
address (int): Starting register address
354
count (int): Number of registers to read
355
srv_info (dict): Server information
356
357
Returns:
358
DataHandler.Return: Response object with success/error status and data
359
"""
360
```
361
362
### DeviceIdentification Class
363
364
Container for device identification objects used in function code 43 responses.
365
366
```python { .api }
367
class DeviceIdentification:
368
"""
369
Device identification information container.
370
371
Parameters:
372
vendor_name (bytes): Vendor name (object ID 0x00, default: b'')
373
product_code (bytes): Product code (object ID 0x01, default: b'')
374
major_minor_revision (bytes): Major/minor revision (object ID 0x02, default: b'')
375
vendor_url (bytes): Vendor URL (object ID 0x03, default: b'')
376
product_name (bytes): Product name (object ID 0x04, default: b'')
377
model_name (bytes): Model name (object ID 0x05, default: b'')
378
user_application_name (bytes): User application name (object ID 0x06, default: b'')
379
objects_id (dict): Additional identification objects (object_id: bytes_value, default: None)
380
"""
381
def __init__(self, vendor_name=b'', product_code=b'', major_minor_revision=b'',
382
vendor_url=b'', product_name=b'', model_name=b'', user_application_name=b'', objects_id=None):
383
"""Initialize device identification with standard objects and optional additional objects."""
384
385
def __getitem__(self, key):
386
"""
387
Get identification object by ID.
388
389
Parameters:
390
key (int): Object ID (0-255)
391
392
Returns:
393
bytes: Object value
394
"""
395
396
def __setitem__(self, key, value):
397
"""
398
Set identification object by ID.
399
400
Parameters:
401
key (int): Object ID (0-255)
402
value (bytes): Object value
403
"""
404
405
def items(self):
406
"""
407
Get all identification objects as key-value pairs.
408
409
Returns:
410
dict_items: All object ID and value pairs
411
"""
412
413
@property
414
def vendor_name(self):
415
"""bytes: Vendor name (object ID 0x00, read-only)."""
416
417
@property
418
def product_code(self):
419
"""bytes: Product code (object ID 0x01, read-only)."""
420
421
@property
422
def major_minor_revision(self):
423
"""bytes: Major/minor revision (object ID 0x02, read-only)."""
424
425
@property
426
def vendor_url(self):
427
"""bytes: Vendor URL (object ID 0x03, read-only)."""
428
429
@property
430
def product_name(self):
431
"""bytes: Product name (object ID 0x04, read-only)."""
432
433
@property
434
def model_name(self):
435
"""bytes: Model name (object ID 0x05, read-only)."""
436
437
@property
438
def user_application_name(self):
439
"""bytes: User application name (object ID 0x06, read-only)."""
440
```
441
442
## Usage Examples
443
444
### Basic Server Setup
445
446
```python
447
from pyModbusTCP.server import ModbusServer, DataBank
448
449
# Create data bank with initial values
450
data_bank = DataBank()
451
data_bank.set_holding_registers(0, [100, 200, 300, 400, 500])
452
data_bank.set_coils(0, [True, False, True, False])
453
454
# Create and start server
455
server = ModbusServer(host="0.0.0.0", port=502, data_bank=data_bank)
456
if server.start():
457
print("Server started successfully")
458
# Server runs in background thread
459
# Call server.stop() when done
460
else:
461
print("Failed to start server")
462
```
463
464
### Custom Data Bank with Change Monitoring
465
466
```python
467
from pyModbusTCP.server import ModbusServer, DataBank
468
469
class MonitoredDataBank(DataBank):
470
def on_holding_registers_change(self, address, from_value, to_value, srv_info):
471
print(f"Registers {address}-{address+len(to_value)-1} changed:")
472
print(f" From: {from_value}")
473
print(f" To: {to_value}")
474
print(f" Client: {srv_info.get('client_addr', 'unknown')}")
475
476
def on_coils_change(self, address, from_value, to_value, srv_info):
477
print(f"Coils {address}-{address+len(to_value)-1} changed:")
478
print(f" From: {from_value}")
479
print(f" To: {to_value}")
480
481
# Use custom data bank
482
custom_data_bank = MonitoredDataBank()
483
server = ModbusServer(data_bank=custom_data_bank)
484
server.start()
485
```
486
487
### Server with Device Identification
488
489
```python
490
from pyModbusTCP.server import ModbusServer, DataBank, DeviceIdentification
491
492
# Create device identification
493
device_id = DeviceIdentification(
494
vendor_name=b'My Company',
495
product_code=b'MC-001',
496
major_minor_revision=b'1.0',
497
vendor_url=b'https://mycompany.com',
498
product_name=b'Industrial Controller',
499
model_name=b'IC-2024',
500
user_application_name=b'Factory Automation'
501
)
502
503
# Configure server with device identification
504
data_bank = DataBank()
505
server = ModbusServer(data_bank=data_bank)
506
server.device_identification = device_id
507
server.start()
508
```
509
510
### Custom Request Handler
511
512
```python
513
from pyModbusTCP.server import ModbusServer, DataBank, DataHandler
514
from pyModbusTCP.constants import EXP_ILLEGAL_FUNCTION
515
516
class CustomDataHandler(DataHandler):
517
def read_h_regs(self, address, count, srv_info):
518
# Custom logic for holding register reads
519
if address >= 1000: # Restrict access to certain addresses
520
return self.Return(exp_code=EXP_ILLEGAL_FUNCTION)
521
522
# Log the request
523
client_addr = srv_info.get('client_addr', 'unknown')
524
print(f"Client {client_addr} reading {count} registers from {address}")
525
526
# Use default implementation
527
return super().read_h_regs(address, count, srv_info)
528
529
def write_h_regs(self, address, words_l, srv_info):
530
# Custom validation for writes
531
if any(w > 1000 for w in words_l): # Limit register values
532
return self.Return(exp_code=EXP_DATA_VALUE)
533
534
return super().write_h_regs(address, words_l, srv_info)
535
536
# Use custom handler
537
data_bank = DataBank()
538
custom_handler = CustomDataHandler(data_bank)
539
server = ModbusServer(data_bank=data_bank, data_hdl=custom_handler)
540
server.start()
541
```
542
543
### Virtual Data Generation
544
545
```python
546
from pyModbusTCP.server import ModbusServer, DataBank
547
import time
548
import threading
549
import random
550
551
class VirtualDataBank(DataBank):
552
def __init__(self):
553
super().__init__()
554
self._running = True
555
self._thread = threading.Thread(target=self._update_data)
556
self._thread.daemon = True
557
self._thread.start()
558
559
def _update_data(self):
560
"""Generate virtual data continuously."""
561
while self._running:
562
# Simulate sensor readings
563
temperature = int(random.uniform(18.0, 25.0) * 10) # Temperature * 10
564
humidity = int(random.uniform(30.0, 70.0))
565
pressure = int(random.uniform(990.0, 1020.0))
566
567
# Update input registers (sensor readings)
568
self.set_input_registers(0, [temperature, humidity, pressure])
569
570
# Update status coils
571
self.set_discrete_inputs(0, [True, temperature > 220, humidity > 60])
572
573
time.sleep(1)
574
575
def stop(self):
576
self._running = False
577
578
# Use virtual data bank
579
virtual_data = VirtualDataBank()
580
server = ModbusServer(data_bank=virtual_data)
581
server.start()
582
```
583
584
## Exception Classes
585
586
```python { .api }
587
class ModbusServer.Error(Exception):
588
"""Base class for server errors."""
589
590
class ModbusServer.NetworkError(Error):
591
"""Network-related server errors."""
592
593
class ModbusServer.DataFormatError(Error):
594
"""Data format validation errors."""
595
596
class DataHandler.ModbusExcept(Exception):
597
"""
598
Exception for Modbus protocol errors in request handling.
599
600
Attributes:
601
exp_code (int): Modbus exception code
602
data (bytes): Optional additional data
603
"""
604
```
605
606
## Container Classes
607
608
```python { .api }
609
class ModbusServer.ClientInfo:
610
"""
611
Container for client connection information.
612
613
Attributes:
614
address (str): Client IP address
615
port (int): Client port number
616
"""
617
618
class ModbusServer.ServerInfo:
619
"""
620
Container for server instance information.
621
622
Attributes:
623
host (str): Server bind address
624
port (int): Server port number
625
"""
626
627
class ModbusServer.SessionData:
628
"""
629
Container for server session data and state information.
630
631
Attributes:
632
client_addr (str): Connected client address
633
unit_id (int): Current request unit ID
634
transaction_id (int): Current transaction ID
635
"""
636
637
class ModbusServer.Frame:
638
"""Raw Modbus frame container."""
639
640
class ModbusServer.MBAP:
641
"""Modbus Application Protocol header container."""
642
643
class ModbusServer.PDU:
644
"""Protocol Data Unit container."""
645
```
646
647
## Types
648
649
```python { .api }
650
class DataHandler.Return:
651
"""
652
Return object for data handler methods.
653
654
Attributes:
655
exp_code (int): Exception code (0 for success, other values indicate specific errors)
656
data (list): Response data (for read operations, None for write operations)
657
658
Methods:
659
ok() -> bool: Returns True if operation was successful (exp_code == 0)
660
"""
661
```