Skip to content

Commit 8b38047

Browse files
author
Chris Hung
authored
add 'change_rate' randomization option (#1229)
* add 'change_rate' randomization option
1 parent 8e04f83 commit 8b38047

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

pymodbus/repl/server/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ def run(
166166
help="Randomize every `r` reads. 0=never, 1=always,2=every-second-read"
167167
", and so on. Applicable IR and DI.",
168168
),
169+
change_rate: int = typer.Option(
170+
0,
171+
"--change-rate",
172+
"-c",
173+
help="Rate in % registers to change. 0=none, 100=all, 12=12% of registers"
174+
", and so on. Applicable IR and DI.",
175+
),
169176
):
170177
"""Run Reactive Modbus server.
171178
@@ -195,6 +202,7 @@ def run(
195202

196203
modbus_config["handler"] = handler
197204
modbus_config["randomize"] = randomize
205+
modbus_config["change_rate"] = change_rate
198206
app = ReactiveServer.factory(
199207
modbus_server,
200208
framer,

pymodbus/server/reactive/main.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def __init__(
113113
holding_registers: BaseModbusDataBlock = None,
114114
zero_mode: bool = False,
115115
randomize: int = 0,
116+
change_rate: int = 0,
116117
**kwargs,
117118
):
118119
"""Reactive Modbus slave context supporting simulating data.
@@ -124,6 +125,8 @@ def __init__(
124125
:param zero_mode: Enable zero mode for data blocks
125126
:param randomize: Randomize reads every <n> reads for DI and IR,
126127
default is disabled (0)
128+
:param change_rate: Rate in % of registers to change for DI and IR,
129+
default is disabled (0)
127130
:param min_binary_value: Minimum value for coils and discrete inputs
128131
:param max_binary_value: Max value for discrete inputs
129132
:param min_register_value: Minimum value for input registers
@@ -142,6 +145,9 @@ def __init__(
142145
min_register_value = kwargs.get("min_register_value", 0)
143146
max_register_value = kwargs.get("max_register_value", 65535)
144147
self._randomize = randomize
148+
self._change_rate = change_rate
149+
if self._randomize > 0:
150+
self._change_rate = 0
145151
self._lock = threading.Lock()
146152
self._read_counter = {"d": 0, "i": 0}
147153
self._min_binary_value = min_binary_value
@@ -177,6 +183,21 @@ def getValues(self, fc_as_hex, address, count=1):
177183
# "'%s'", _block_type, values, address)
178184
self.store[_block_type].setValues(address, values)
179185
self._read_counter[_block_type] += 1
186+
elif self._change_rate > 0 and _block_type in {"d", "i"}:
187+
regs_to_changes = round(count * self._change_rate / 100)
188+
random_indices = random.sample(range(count), regs_to_changes)
189+
for offset in random_indices:
190+
with self._lock:
191+
# Update values
192+
if _block_type == "d":
193+
min_val = self._min_binary_value
194+
max_val = self._max_binary_value
195+
else:
196+
min_val = self._min_register_value
197+
max_val = self._max_register_value
198+
self.store[_block_type].setValues(
199+
address + offset, random.randint(min_val, max_val)
200+
)
180201
values = self.store[_block_type].getValues(address, count)
181202
return values
182203

@@ -385,13 +406,15 @@ def create_context(
385406
unit: list[int] = [1],
386407
single: bool = False,
387408
randomize: int = 0,
409+
change_rate: int = 0,
388410
): # pylint: disable=dangerous-default-value
389411
"""Create Modbus context.
390412
391413
:param data_block_settings: Datablock (dict) Refer DEFAULT_DATA_BLOCK
392414
:param unit: Unit id for the slave
393415
:param single: To run as a single slave
394416
:param randomize: Randomize every <n> reads for DI and IR.
417+
:param change_rate: Rate in % of registers to change for DI and IR.
395418
:return: ModbusServerContext object
396419
"""
397420
data_block = data_block_settings.pop("data_block", DEFAULT_DATA_BLOCK)
@@ -422,7 +445,11 @@ def create_context(
422445
block[modbus_entity] = db(start_address, default_values)
423446

424447
slave_context = ReactiveModbusSlaveContext(
425-
**block, randomize=randomize, zero_mode=True, **data_block_settings
448+
**block,
449+
randomize=randomize,
450+
change_rate=change_rate,
451+
zero_mode=True,
452+
**data_block_settings,
426453
)
427454
if not single:
428455
slaves[i] = slave_context
@@ -471,6 +498,7 @@ def factory( # pylint: disable=dangerous-default-value,too-many-arguments
471498
sys.exit(1)
472499
server = SERVER_MAPPER.get(server)
473500
randomize = kwargs.pop("randomize", 0)
501+
change_rate = kwargs.pop("change_rate", 0)
474502
if not framer:
475503
framer = DEFAULT_FRAMER.get(server)
476504
if not context:
@@ -479,6 +507,7 @@ def factory( # pylint: disable=dangerous-default-value,too-many-arguments
479507
unit=unit,
480508
single=single,
481509
randomize=randomize,
510+
change_rate=change_rate,
482511
)
483512
if not identity:
484513
identity = cls.create_identity()

0 commit comments

Comments
 (0)