0
# Data Store
1
2
PyModbus provides flexible data storage implementations for server contexts, supporting both sequential and sparse memory layouts with full customization capabilities. The datastore system manages coils, discrete inputs, holding registers, and input registers with configurable addressing modes.
3
4
## Capabilities
5
6
### Server Context Management
7
8
Classes for managing server data contexts and device instances.
9
10
```python { .api }
11
class ModbusServerContext:
12
def __init__(self, device_default=None, single=False, **kwargs):
13
"""
14
Server context managing multiple device contexts.
15
16
Parameters:
17
- device_default: Default device context for single device mode
18
- single (bool): True for single device mode, False for multi-device
19
- **kwargs: Additional context parameters
20
"""
21
22
def __getitem__(self, device_id: int):
23
"""Get device context by device ID."""
24
25
def __setitem__(self, device_id: int, context):
26
"""Set device context for device ID."""
27
28
def __delitem__(self, device_id: int):
29
"""Remove device context for device ID."""
30
31
def __contains__(self, device_id: int) -> bool:
32
"""Check if device ID exists in context."""
33
34
def __iter__(self):
35
"""Iterate over device IDs."""
36
37
class ModbusDeviceContext:
38
def __init__(self, co=None, di=None, hr=None, ir=None, zero_mode=False):
39
"""
40
Device context managing data stores for different data types.
41
42
Parameters:
43
- co: Coils data store
44
- di: Discrete inputs data store
45
- hr: Holding registers data store
46
- ir: Input registers data store
47
- zero_mode (bool): Use zero-based addressing if True
48
"""
49
50
def validate(self, fx: int, address: int, count: int = 1) -> bool:
51
"""
52
Validate data store access.
53
54
Parameters:
55
- fx (int): Function code (1=coils, 2=discrete inputs, 3=holding regs, 4=input regs)
56
- address (int): Starting address
57
- count (int): Number of values
58
59
Returns:
60
bool: True if access is valid
61
"""
62
63
def getValues(self, fx: int, address: int, count: int = 1) -> list:
64
"""
65
Get values from data store.
66
67
Parameters:
68
- fx (int): Function code
69
- address (int): Starting address
70
- count (int): Number of values to read
71
72
Returns:
73
list: Retrieved values
74
"""
75
76
def setValues(self, fx: int, address: int, values: list):
77
"""
78
Set values in data store.
79
80
Parameters:
81
- fx (int): Function code
82
- address (int): Starting address
83
- values (list): Values to write
84
"""
85
86
class ModbusBaseDeviceContext:
87
def decode(self, fx: int) -> str:
88
"""Decode function code to data type name."""
89
90
def encode(self, fx: int) -> int:
91
"""Encode data type name to function code."""
92
93
def reset(self):
94
"""Reset all data stores to default values."""
95
96
def register(self, fx: int, slave_id: int = 0x00, func=None):
97
"""Register custom function handler."""
98
99
def validate(self, fx: int, address: int, count: int = 1) -> bool:
100
"""Validate data store access."""
101
102
def getValues(self, fx: int, address: int, count: int = 1) -> list:
103
"""Get values from data store."""
104
105
def setValues(self, fx: int, address: int, values: list):
106
"""Set values in data store."""
107
```
108
109
### Data Block Implementations
110
111
Different data storage backends for various use cases.
112
113
```python { .api }
114
class ModbusSequentialDataBlock:
115
def __init__(self, address: int, values: list):
116
"""
117
Sequential data block with contiguous memory layout.
118
119
Parameters:
120
- address (int): Starting address
121
- values (list): Initial values (list of integers for registers, booleans for coils)
122
"""
123
124
def validate(self, address: int, count: int = 1) -> bool:
125
"""
126
Validate address range access.
127
128
Parameters:
129
- address (int): Starting address
130
- count (int): Number of values
131
132
Returns:
133
bool: True if address range is valid
134
"""
135
136
def getValues(self, address: int, count: int = 1) -> list:
137
"""
138
Get values from the data block.
139
140
Parameters:
141
- address (int): Starting address
142
- count (int): Number of values to read
143
144
Returns:
145
list: Retrieved values
146
"""
147
148
def setValues(self, address: int, values: list):
149
"""
150
Set values in the data block.
151
152
Parameters:
153
- address (int): Starting address
154
- values (list): Values to write
155
"""
156
157
def reset(self):
158
"""Reset all values to 0."""
159
160
class ModbusSparseDataBlock:
161
def __init__(self, values: dict = None):
162
"""
163
Sparse data block with dictionary-based storage.
164
165
Parameters:
166
- values (dict): Dictionary mapping addresses to values
167
"""
168
169
def validate(self, address: int, count: int = 1) -> bool:
170
"""Validate address range access."""
171
172
def getValues(self, address: int, count: int = 1) -> list:
173
"""Get values from specific addresses."""
174
175
def setValues(self, address: int, values: list):
176
"""Set values at specific addresses."""
177
178
def reset(self):
179
"""Clear all stored values."""
180
```
181
182
### Simulator Context
183
184
Specialized context for simulation with advanced features.
185
186
```python { .api }
187
class ModbusSimulatorContext:
188
def __init__(self, config: dict = None, custom_actions_module=None):
189
"""
190
Simulator context with configurable behavior.
191
192
Parameters:
193
- config (dict): Simulation configuration
194
- custom_actions_module: Module containing custom action functions
195
"""
196
197
def validate(self, fx: int, address: int, count: int = 1) -> bool:
198
"""Validate access with simulation rules."""
199
200
def getValues(self, fx: int, address: int, count: int = 1) -> list:
201
"""Get values with simulation behavior."""
202
203
def setValues(self, fx: int, address: int, values: list):
204
"""Set values with simulation actions."""
205
206
def apply_actions(self, fx: int, address: int, values: list):
207
"""Apply configured simulation actions."""
208
209
def reset(self):
210
"""Reset simulator to initial state."""
211
```
212
213
## Usage Examples
214
215
### Basic Sequential Data Store
216
217
```python
218
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusDeviceContext, ModbusServerContext
219
220
# Create data blocks for different data types
221
coils = ModbusSequentialDataBlock(0, [False] * 100) # 100 coils starting at address 0
222
discrete_inputs = ModbusSequentialDataBlock(0, [False] * 100) # 100 discrete inputs
223
holding_registers = ModbusSequentialDataBlock(0, [0] * 100) # 100 holding registers
224
input_registers = ModbusSequentialDataBlock(0, [0] * 100) # 100 input registers
225
226
# Create device context with data stores
227
device_context = ModbusDeviceContext(
228
co=coils, # coils
229
di=discrete_inputs, # discrete inputs
230
hr=holding_registers, # holding registers
231
ir=input_registers # input registers
232
)
233
234
# Create server context (single device mode)
235
server_context = ModbusServerContext(device_default=device_context, single=True)
236
```
237
238
### Multi-Device Data Store
239
240
```python
241
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusDeviceContext, ModbusServerContext
242
243
# Create different data stores for different devices
244
def create_device_context(initial_values):
245
store = ModbusSequentialDataBlock(0, initial_values)
246
return ModbusDeviceContext(co=store, di=store, hr=store, ir=store)
247
248
# Device 1: Sensor data
249
device1 = create_device_context([10, 20, 30, 40, 50])
250
251
# Device 2: Actuator controls
252
device2 = create_device_context([100, 200, 300, 400, 500])
253
254
# Device 3: System status
255
device3 = create_device_context([1, 0, 1, 1, 0])
256
257
# Create multi-device server context
258
server_context = ModbusServerContext(single=False)
259
server_context[1] = device1 # Device ID 1
260
server_context[2] = device2 # Device ID 2
261
server_context[3] = device3 # Device ID 3
262
263
# Check device availability
264
if 1 in server_context:
265
print("Device 1 is available")
266
267
# Get device context
268
device1_context = server_context[1]
269
values = device1_context.getValues(3, 0, 5) # Read holding registers
270
print(f"Device 1 holding registers: {values}")
271
```
272
273
### Sparse Data Store
274
275
```python
276
from pymodbus.datastore import ModbusSparseDataBlock, ModbusDeviceContext, ModbusServerContext
277
278
# Create sparse data blocks with non-contiguous addresses
279
sparse_coils = ModbusSparseDataBlock({
280
0: True, # Address 0
281
10: False, # Address 10
282
100: True, # Address 100
283
1000: False # Address 1000
284
})
285
286
sparse_registers = ModbusSparseDataBlock({
287
0: 12345, # Address 0
288
50: 67890, # Address 50
289
500: 11111, # Address 500
290
5000: 22222 # Address 5000
291
})
292
293
# Create device context with sparse stores
294
device_context = ModbusDeviceContext(
295
co=sparse_coils,
296
di=sparse_coils,
297
hr=sparse_registers,
298
ir=sparse_registers
299
)
300
301
server_context = ModbusServerContext(device_default=device_context, single=True)
302
303
# Access sparse data
304
print(f"Coil at address 0: {device_context.getValues(1, 0, 1)}")
305
print(f"Register at address 50: {device_context.getValues(3, 50, 1)}")
306
```
307
308
### Custom Data Store Implementation
309
310
```python
311
from pymodbus.datastore import ModbusDeviceContext, ModbusServerContext
312
313
class DatabaseDataBlock:
314
"""Custom data block that stores values in a database."""
315
316
def __init__(self, table_name, db_connection):
317
self.table_name = table_name
318
self.db = db_connection
319
320
def validate(self, address, count=1):
321
# Check if addresses exist in database
322
return True # Simplified validation
323
324
def getValues(self, address, count=1):
325
# Read values from database
326
cursor = self.db.cursor()
327
cursor.execute(
328
f"SELECT value FROM {self.table_name} WHERE address BETWEEN ? AND ? ORDER BY address",
329
(address, address + count - 1)
330
)
331
results = cursor.fetchall()
332
return [row[0] for row in results]
333
334
def setValues(self, address, values):
335
# Write values to database
336
cursor = self.db.cursor()
337
for i, value in enumerate(values):
338
cursor.execute(
339
f"INSERT OR REPLACE INTO {self.table_name} (address, value) VALUES (?, ?)",
340
(address + i, value)
341
)
342
self.db.commit()
343
344
# Usage (assuming database connection exists)
345
# db_store = DatabaseDataBlock('modbus_registers', db_connection)
346
# device_context = ModbusDeviceContext(hr=db_store, ir=db_store)
347
```
348
349
### Data Store with Validation
350
351
```python
352
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusDeviceContext
353
354
class ValidatedDataBlock(ModbusSequentialDataBlock):
355
"""Data block with value validation."""
356
357
def __init__(self, address, values, min_value=0, max_value=65535):
358
super().__init__(address, values)
359
self.min_value = min_value
360
self.max_value = max_value
361
362
def setValues(self, address, values):
363
# Validate values before storing
364
validated_values = []
365
for value in values:
366
if isinstance(value, bool):
367
validated_values.append(value)
368
else:
369
# Clamp numeric values to valid range
370
clamped = max(self.min_value, min(self.max_value, int(value)))
371
validated_values.append(clamped)
372
373
super().setValues(address, validated_values)
374
375
# Create validated data store
376
validated_registers = ValidatedDataBlock(0, [0] * 100, min_value=0, max_value=1000)
377
device_context = ModbusDeviceContext(hr=validated_registers)
378
379
# Values will be clamped to valid range
380
device_context.setValues(3, 0, [500, 1500, -100]) # [500, 1000, 0]
381
```
382
383
### Zero-Based vs One-Based Addressing
384
385
```python
386
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusDeviceContext
387
388
# Create data store
389
registers = ModbusSequentialDataBlock(0, list(range(100)))
390
391
# Zero-based addressing (default)
392
zero_based_context = ModbusDeviceContext(hr=registers, zero_mode=True)
393
394
# One-based addressing (traditional Modbus)
395
one_based_context = ModbusDeviceContext(hr=registers, zero_mode=False)
396
397
# With zero_mode=True, address 0 maps to first element
398
zero_values = zero_based_context.getValues(3, 0, 5) # Gets elements [0,1,2,3,4]
399
400
# With zero_mode=False, address 1 maps to first element
401
one_values = one_based_context.getValues(3, 1, 5) # Gets elements [0,1,2,3,4]
402
403
print(f"Zero-based: {zero_values}")
404
print(f"One-based: {one_values}")
405
```
406
407
### Dynamic Data Store Updates
408
409
```python
410
import threading
411
import time
412
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusDeviceContext, ModbusServerContext
413
414
# Create data store
415
registers = ModbusSequentialDataBlock(0, [0] * 100)
416
device_context = ModbusDeviceContext(hr=registers)
417
server_context = ModbusServerContext(device_default=device_context, single=True)
418
419
def update_simulation_data():
420
"""Background thread to update simulation data."""
421
counter = 0
422
while True:
423
# Update some register values
424
device_context.setValues(3, 0, [counter]) # Counter at address 0
425
device_context.setValues(3, 1, [counter % 100]) # Modulo counter at address 1
426
device_context.setValues(3, 2, [int(time.time()) % 65536]) # Timestamp at address 2
427
428
counter += 1
429
time.sleep(1)
430
431
# Start background update thread
432
update_thread = threading.Thread(target=update_simulation_data, daemon=True)
433
update_thread.start()
434
435
# Server context now has dynamically updating data
436
```
437
438
### Simulator Context Configuration
439
440
```python
441
from pymodbus.datastore import ModbusSimulatorContext
442
443
# Simulator configuration
444
simulator_config = {
445
"setup": {
446
"co size": 100,
447
"di size": 100,
448
"hr size": 100,
449
"ir size": 100
450
},
451
"invalid": [
452
{"device": 1, "function": 3, "address": [5, 6]}, # Invalid addresses
453
],
454
"write": [
455
{"device": 1, "function": 16, "address": 10, "action": "random"} # Random values on write
456
],
457
"repeat": [
458
{"device": 1, "function": 3, "address": 20, "period": 5} # Update every 5 seconds
459
]
460
}
461
462
# Create simulator context
463
simulator_context = ModbusSimulatorContext(config=simulator_config)
464
465
# Simulator will handle reads/writes according to configuration
466
```
467
468
### Function Code Constants
469
470
The datastore system uses function codes to identify data types:
471
472
```python
473
# Function codes for data store access
474
FUNCTION_CODES = {
475
1: "coils", # Read Coils
476
2: "discrete_inputs", # Read Discrete Inputs
477
3: "holding_registers", # Read Holding Registers
478
4: "input_registers", # Read Input Registers
479
5: "coils", # Write Single Coil
480
6: "holding_registers", # Write Single Register
481
15: "coils", # Write Multiple Coils
482
16: "holding_registers" # Write Multiple Registers
483
}
484
485
# Example of function code usage
486
device_context = ModbusDeviceContext(hr=registers)
487
values = device_context.getValues(3, 0, 10) # Function code 3 = holding registers
488
device_context.setValues(3, 0, [1, 2, 3]) # Write to holding registers
489
```