@@ -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