11"""Pymodbus ModbusSimulatorContext."""
22import dataclasses
33import logging
4+ import random
45import struct
56import sys
7+ from datetime import datetime
68from typing import Callable , Dict
79
810
@@ -122,39 +124,45 @@ def __init__(self):
122124 },
123125 }
124126
125- def handle_type_bits (self , registers , start , stop , value , action ):
127+ def handle_type_bits (self , registers , reg_count , start , stop , value , action ):
126128 """Handle type bits.
127129
128130 :meta private:
129131 """
130132 for i in range (start , stop ):
133+ if i >= reg_count :
134+ raise RuntimeError (f"Error section \" { Label .type_bits } \" addr { start } , { stop } out of range" )
131135 if registers [i ].type != CELL_TYPE_NONE :
132136 txt = f'ERROR Configuration invalid in section "{ Label .type_bits } " register { i } already defined'
133137 raise RuntimeError (txt )
134138 registers [i ].value = value
135139 registers [i ].type = CELL_TYPE_BIT
136140 registers [i ].action = action
137141
138- def handle_type_uint16 (self , registers , start , stop , value , action ):
142+ def handle_type_uint16 (self , registers , reg_count , start , stop , value , action ):
139143 """Handle type uint16.
140144
141145 :meta private:
142146 """
143147 for i in range (start , stop ):
148+ if i >= reg_count :
149+ raise RuntimeError (f"Error section \" { Label .type_uint16 } \" addr { start } , { stop } out of range" )
144150 if registers [i ].type != CELL_TYPE_NONE :
145151 txt = f'ERROR Configuration invalid in section "{ Label .type_uint16 } " register { i } already defined'
146152 raise RuntimeError (txt )
147153 registers [i ].value = value
148154 registers [i ].type = CELL_TYPE_UINT16
149155 registers [i ].action = action
150156
151- def handle_type_uint32 (self , registers , start , stop , value , action ):
157+ def handle_type_uint32 (self , registers , reg_count , start , stop , value , action ):
152158 """Handle type uint32.
153159
154160 :meta private:
155161 """
156162 regs = ModbusSimulatorContext .build_registers_from_value (value , True )
157163 for i in range (start , stop , 2 ):
164+ if i + 1 >= reg_count :
165+ raise RuntimeError (f"Error section \" { Label .type_uint32 } \" addr { start } , { stop } out of range" )
158166 if registers [i ].type != CELL_TYPE_NONE :
159167 txt = f'ERROR Configuration invalid in section "{ Label .type_uint32 } " register { i } already defined'
160168 raise RuntimeError (txt )
@@ -168,13 +176,15 @@ def handle_type_uint32(self, registers, start, stop, value, action):
168176 registers [j ].value = regs [1 ]
169177 registers [j ].type = CELL_TYPE_NEXT
170178
171- def handle_type_float32 (self , registers , start , stop , value , action ):
179+ def handle_type_float32 (self , registers , reg_count , start , stop , value , action ):
172180 """Handle type uint32.
173181
174182 :meta private:
175183 """
176184 regs = ModbusSimulatorContext .build_registers_from_value (value , False )
177185 for i in range (start , stop , 2 ):
186+ if i + 1 >= reg_count :
187+ raise RuntimeError (f"Error section \" { Label .type_float32 } \" addr { start } , { stop } out of range" )
178188 if registers [i ].type != CELL_TYPE_NONE :
179189 txt = f'ERROR Configuration invalid in section "{ Label .type_float32 } " register { i } already defined'
180190 raise RuntimeError (txt )
@@ -188,7 +198,7 @@ def handle_type_float32(self, registers, start, stop, value, action):
188198 registers [j ].value = regs [1 ]
189199 registers [j ].type = CELL_TYPE_NEXT
190200
191- def handle_type_string (self , registers , start , stop , value , action ):
201+ def handle_type_string (self , registers , reg_count , start , stop , value , action ):
192202 """Handle type string.
193203
194204 :meta private:
@@ -201,6 +211,8 @@ def handle_type_string(self, registers, start, stop, value, action):
201211 value = value .ljust (reg_len )
202212 for i in range (stop - start ):
203213 inx = start + i
214+ if i + 1 >= reg_count :
215+ raise RuntimeError (f"Error section \" { Label .type_start } \" addr { start } , { stop } out of range" )
204216 if registers [inx ].type != CELL_TYPE_NONE :
205217 txt = f'ERROR Configuration invalid in section "{ Label .type_string } " register { inx } already defined'
206218 raise RuntimeError (txt )
@@ -261,26 +273,30 @@ def handle_setup_section(self, config, actions):
261273 entry [Label .action ] = action
262274 return registers , offset , type_exception
263275
264- def handle_invalid_address (self , registers , config ):
276+ def handle_invalid_address (self , registers , reg_count , config ):
265277 """Handle invalid address"""
266278 for entry in Label .try_get (Label .invalid , config ):
267279 if isinstance (entry , int ):
268280 entry = [entry , entry ]
269281 for i in range (entry [0 ], entry [1 ] + 1 ):
282+ if i >= reg_count :
283+ raise RuntimeError (f"Error section \" { Label .invalid } \" addr { entry } out of range" )
270284 if registers [i ].type != CELL_TYPE_NONE :
271285 txt = f'ERROR Configuration invalid in section "invalid" register { i } already defined'
272286 raise RuntimeError (txt )
273287 registers [i ].type = CELL_TYPE_ILLEGAL
274288
275- def handle_write_allowed (self , registers , config ):
289+ def handle_write_allowed (self , registers , reg_count , config ):
276290 """Handle write allowed"""
277291 for entry in Label .try_get (Label .write , config ):
278292 if isinstance (entry , int ):
279293 entry = [entry , entry ]
280294 for i in range (entry [0 ], entry [1 ] + 1 ):
295+ if i >= reg_count :
296+ raise RuntimeError (f"Error section \" { Label .write } \" addr { entry } out of range" )
281297 registers [i ].access = True
282298
283- def handle_types (self , registers , actions , config ):
299+ def handle_types (self , registers , actions , reg_count , config ):
284300 """Handle the different types"""
285301 for section , type_entry in self .config_types .items ():
286302 layout = Label .try_get (section , config )
@@ -291,13 +307,14 @@ def handle_types(self, registers, actions, config):
291307 entry [Label .addr ] = [entry [Label .addr ], entry [Label .addr ]]
292308 type_entry [Label .method ](
293309 registers ,
310+ reg_count ,
294311 entry [Label .addr ][0 ],
295312 entry [Label .addr ][1 ] + 1 ,
296313 entry .get (Label .value , type_entry [Label .value ]),
297314 actions [entry .get ("action" , type_entry [Label .action ])],
298315 )
299316
300- def handle_repeat (self , registers , config ):
317+ def handle_repeat (self , registers , reg_count , config ):
301318 """Handle repeat.
302319
303320 :meta private:
@@ -310,6 +327,8 @@ def handle_repeat(self, registers, config):
310327 addr_to = Label .try_get (Label .repeat_to , entry )
311328 for inx in range (addr_to [0 ], addr_to [1 ] + 1 ):
312329 copy_inx = copy_start if copy_inx >= copy_end else copy_inx + 1
330+ if inx >= reg_count :
331+ raise RuntimeError (f"Error section \" { Label .repeat } \" entry { entry } out of range" )
313332 registers [inx ] = dataclasses .replace (registers [copy_inx ])
314333
315334 def setup (self , config , actions , custom_actions ) -> None :
@@ -323,11 +342,11 @@ def setup(self, config, actions, custom_actions) -> None:
323342 actions .update (custom_actions )
324343
325344 registers , offset , typ_exc = self .handle_setup_section (config , actions )
326- self .handle_invalid_address (registers , config )
327- self .handle_write_allowed (registers , config )
328- self .handle_types (registers , actions , config )
329- self .handle_repeat (registers , config )
330345 reg_count = len (registers )
346+ self .handle_invalid_address (registers , reg_count , config )
347+ self .handle_write_allowed (registers , reg_count , config )
348+ self .handle_types (registers , actions , reg_count , config )
349+ self .handle_repeat (registers , reg_count , config )
331350 for i in range (reg_count ):
332351 if registers [i ].type == CELL_TYPE_NONE :
333352 registers [i ].type = CELL_TYPE_ILLEGAL
@@ -484,7 +503,7 @@ def getValues(self, func_code, address, count=1): # pylint: disable=invalid-nam
484503 for i in range (real_address , real_address + count ):
485504 reg = self .registers [i ]
486505 if reg .action :
487- reg .action (i )
506+ reg .action (i , reg )
488507 result .append (reg .value )
489508 else :
490509 # bit access
@@ -494,7 +513,7 @@ def getValues(self, func_code, address, count=1): # pylint: disable=invalid-nam
494513 for i in range (real_address , real_address + reg_count ):
495514 reg = self .registers [i ]
496515 if reg .action :
497- reg .action (i )
516+ reg .action (i , reg )
498517 while count and bit_index < 16 :
499518 result .append (bool (reg .value & (2 ** bit_index )))
500519 count -= 1
@@ -533,21 +552,66 @@ def setValues(self, func_code, address, values): # pylint: disable=invalid-name
533552 # Internal action methods
534553 # --------------------------------------------
535554
536- @classmethod
537- def action_random (cls , inx ):
538- """Update with random value."""
555+ def action_random (self , inx , cell ):
556+ """Update with random value.
539557
540- @classmethod
541- def action_increment (cls , inx ):
542- """Increment value reset with overflow."""
558+ :meta private:
559+ """
560+ if cell .type == CELL_TYPE_BIT :
561+ self .registers [inx ].value = random .randint (0 , 65536 )
562+ elif cell .type == CELL_TYPE_FLOAT32 :
563+ regs = self .build_registers_from_value (random .uniform (0.0 , 100.0 ), False )
564+ self .registers [inx ].value = regs [0 ]
565+ self .registers [inx + 1 ].value = regs [1 ]
566+ elif cell .type == CELL_TYPE_UINT16 :
567+ self .registers [inx ].value = random .randint (0 , 65536 )
568+ elif cell .type == CELL_TYPE_UINT32 :
569+ regs = self .build_registers_from_value (random .uniform (0.0 , 100.0 ), True )
570+ self .registers [inx ].value = regs [0 ]
571+ self .registers [inx + 1 ].value = regs [1 ]
572+
573+ def action_increment (self , inx , cell ):
574+ """Increment value reset with overflow.
543575
544- @classmethod
545- def action_timestamp (cls , inx ):
546- """Set current time."""
576+ :meta private:
577+ """
578+ if cell .type == CELL_TYPE_BIT :
579+ self .registers [inx ].value += 1
580+ elif cell .type == CELL_TYPE_FLOAT32 :
581+ value = self .build_value_from_registers (self .registers [inx : inx + 2 ])
582+ value += 1.0
583+ regs = self .build_registers_from_value (value , False )
584+ self .registers [inx ].value = regs [0 ]
585+ self .registers [inx + 1 ].value = regs [1 ]
586+ elif cell .type == CELL_TYPE_UINT16 :
587+ self .registers [inx ].value += 1
588+ elif cell .type == CELL_TYPE_UINT32 :
589+ value = self .build_value_from_registers (self .registers [inx : inx + 2 ])
590+ value += 1
591+ regs = self .build_registers_from_value (value , True )
592+ self .registers [inx ].value = regs [0 ]
593+ self .registers [inx + 1 ].value = regs [1 ]
594+
595+ def action_timestamp (self , inx , _cell ):
596+ """Set current time.
547597
548- @classmethod
549- def action_reset (cls , inx ):
550- """Reboot server."""
598+ :meta private:
599+ """
600+ x = datetime .now ()
601+ self .registers [inx ].value = x .tm_year - 1900
602+ self .registers [inx + 1 ].value = x .tm_mon
603+ self .registers [inx + 2 ].value = x .tm_mday
604+ self .registers [inx + 3 ].value = x .tm_wday
605+ self .registers [inx + 4 ].value = x .tm_hour
606+ self .registers [inx + 5 ].value = x .tm_min
607+ self .registers [inx + 1 ].value = x .tm_sec
608+
609+ def action_reset (self , _inx , _cell ):
610+ """Reboot server.
611+
612+ :meta private:
613+ """
614+ raise RuntimeError ("RESET server" )
551615
552616 # --------------------------------------------
553617 # Internal helper methods
0 commit comments