1818CELL_TYPE_FLOAT32 = "F"
1919CELL_TYPE_STRING = "S"
2020CELL_TYPE_NEXT = "n"
21- CELL_TYPE_ILLEGAL = "X"
21+ CELL_TYPE_INVALID = "X"
2222
2323WORD_SIZE = 16
2424
@@ -30,7 +30,9 @@ class Cell:
3030 type : int = CELL_TYPE_NONE
3131 access : bool = False
3232 value : int = 0
33- action : Callable = None
33+ action : int = 0
34+ count_read : int = 0
35+ count_write : int = 0
3436
3537
3638@dataclasses .dataclass
@@ -42,6 +44,7 @@ class Label: # pylint: disable=too-many-instance-attributes
4244
4345 action : str = "action"
4446 addr : str = "addr"
47+ any : str = "any"
4548 co_size : str = "co size"
4649 defaults : str = "defaults"
4750 di_size : str = "di size"
@@ -52,6 +55,7 @@ class Label: # pylint: disable=too-many-instance-attributes
5255 method : str = "method"
5356 next : str = "next"
5457 random : str = "random"
58+ register : str = "register"
5559 repeat : str = "repeat"
5660 reset : str = "reset"
5761 setup : str = "setup"
@@ -231,7 +235,7 @@ def handle_type_string(self, registers, reg_count, start, stop, value, action):
231235 registers [inx ].value = int .from_bytes (
232236 bytes (value [i * 2 : (i + 1 ) * 2 ], "UTF-8" ), "big"
233237 )
234- registers [inx ].type = CELL_TYPE_STRING
238+ registers [inx ].type = CELL_TYPE_STRING if not i else CELL_TYPE_NEXT
235239 registers [start ].action = action
236240
237241 def handle_setup_section (self , config , actions ):
@@ -300,7 +304,7 @@ def handle_invalid_address(self, registers, reg_count, config):
300304 if registers [i ].type != CELL_TYPE_NONE :
301305 txt = f'ERROR Configuration invalid in section "invalid" register { i } already defined'
302306 raise RuntimeError (txt )
303- registers [i ].type = CELL_TYPE_ILLEGAL
307+ registers [i ].type = CELL_TYPE_INVALID
304308
305309 def handle_write_allowed (self , registers , reg_count , config ):
306310 """Handle write allowed"""
@@ -312,6 +316,9 @@ def handle_write_allowed(self, registers, reg_count, config):
312316 raise RuntimeError (
313317 f'Error section "{ Label .write } " addr { entry } out of range'
314318 )
319+ if registers [i ].type in (CELL_TYPE_NONE , CELL_TYPE_INVALID ):
320+ txt = f'ERROR Configuration invalid in section "write" register { i } not defined'
321+ raise RuntimeError (txt )
315322 registers [i ].access = True
316323
317324 def handle_types (self , registers , actions , reg_count , config ):
@@ -351,25 +358,20 @@ def handle_repeat(self, registers, reg_count, config):
351358 )
352359 registers [inx ] = dataclasses .replace (registers [copy_inx ])
353360
354- def setup (self , config , actions , custom_actions ) -> None :
361+ def setup (self , config , actions ) -> None :
355362 """Load layout from dict with json structure.
356363
357364 :meta private:
358365 """
359- actions ["" ] = None
360- actions [None ] = None
361- if custom_actions :
362- actions .update (custom_actions )
363-
364366 registers , offset , typ_exc = self .handle_setup_section (config , actions )
365367 reg_count = len (registers )
366368 self .handle_invalid_address (registers , reg_count , config )
367- self .handle_write_allowed (registers , reg_count , config )
368369 self .handle_types (registers , actions , reg_count , config )
370+ self .handle_write_allowed (registers , reg_count , config )
369371 self .handle_repeat (registers , reg_count , config )
370372 for i in range (reg_count ):
371373 if registers [i ].type == CELL_TYPE_NONE :
372- registers [i ].type = CELL_TYPE_ILLEGAL
374+ registers [i ].type = CELL_TYPE_INVALID
373375
374376 return (registers , offset , typ_exc , reg_count )
375377
@@ -468,16 +470,42 @@ class ModbusSimulatorContext:
468470 # --------------------------------------------
469471 start_time = int (datetime .now ().timestamp ())
470472
471- def __init__ (self , config : Dict [str , any ], actions : Dict [str , Callable ]) -> None :
473+ def __init__ (
474+ self , config : Dict [str , any ], custom_actions : Dict [str , Callable ]
475+ ) -> None :
472476 """Initialize."""
473- builtin_actions = {
477+ self . action_names = {
474478 Label .increment : self .action_increment ,
479+ Label .register : self .action_register ,
475480 Label .random : self .action_random ,
476481 Label .reset : self .action_reset ,
477482 Label .timestamp : self .action_timestamp ,
478483 Label .uptime : self .action_uptime ,
479484 }
480- res = Setup ().setup (config , builtin_actions , actions )
485+ if custom_actions :
486+ self .action_names .update (custom_actions )
487+ j = len (self .action_names ) + 1
488+ self .action_inx_to_name = ["None" ] * j
489+ self .action_methods = [None ] * j
490+ j = 1
491+ for key , method in self .action_names .items ():
492+ self .action_inx_to_name [j ] = key
493+ self .action_methods [j ] = method
494+ self .action_names [key ] = j
495+ j += 1
496+ self .action_names [None ] = 0
497+ self .type_names = {
498+ Label .type_none : CELL_TYPE_NONE ,
499+ Label .type_bits : CELL_TYPE_BIT ,
500+ Label .type_uint16 : CELL_TYPE_UINT16 ,
501+ Label .type_uint32 : CELL_TYPE_UINT32 ,
502+ Label .type_float32 : CELL_TYPE_FLOAT32 ,
503+ Label .type_string : CELL_TYPE_STRING ,
504+ Label .next : CELL_TYPE_NEXT ,
505+ Label .invalid : CELL_TYPE_INVALID ,
506+ Label .any : None ,
507+ }
508+ res = Setup ().setup (config , self .action_names )
481509 self .registers = res [0 ]
482510 self .offset = res [1 ]
483511 self .type_exception = res [2 ]
@@ -506,7 +534,7 @@ def validate(self, func_code, address, count=1):
506534 fx_write = func_code in self ._write_func_code
507535 for i in range (real_address , real_address + count ):
508536 reg = self .registers [i ]
509- if reg .type == CELL_TYPE_ILLEGAL :
537+ if reg .type == CELL_TYPE_INVALID :
510538 return False
511539 if fx_write and not reg .access :
512540 return False
@@ -525,7 +553,8 @@ def getValues(self, func_code, address, count=1): # pylint: disable=invalid-nam
525553 for i in range (real_address , real_address + count ):
526554 reg = self .registers [i ]
527555 if reg .action :
528- reg .action (self .registers , i , reg )
556+ self .action_methods [reg .action ](self .registers , i , reg )
557+ self .registers [i ].count_read += 1
529558 result .append (reg .value )
530559 else :
531560 # bit access
@@ -535,7 +564,8 @@ def getValues(self, func_code, address, count=1): # pylint: disable=invalid-nam
535564 for i in range (real_address , real_address + reg_count ):
536565 reg = self .registers [i ]
537566 if reg .action :
538- reg .action (i , reg )
567+ self .action_methods [reg .action ](i , reg )
568+ self .registers [i ].count_read += 1
539569 while count and bit_index < 16 :
540570 result .append (bool (reg .value & (2 ** bit_index )))
541571 count -= 1
@@ -552,6 +582,7 @@ def setValues(self, func_code, address, values): # pylint: disable=invalid-name
552582 real_address = self .offset [func_code ] + address
553583 for value in values :
554584 self .registers [real_address ].value = value
585+ self .registers [real_address ].count_write += 1
555586 real_address += 1
556587 return
557588
@@ -564,6 +595,7 @@ def setValues(self, func_code, address, values): # pylint: disable=invalid-name
564595 self .registers [real_address ].value |= bit_mask
565596 else :
566597 self .registers [real_address ].value &= ~ bit_mask
598+ self .registers [real_address ].count_write += 1
567599 bit_index += 1
568600 if bit_index == 16 :
569601 bit_index = 0
@@ -574,6 +606,25 @@ def setValues(self, func_code, address, values): # pylint: disable=invalid-name
574606 # Internal action methods
575607 # --------------------------------------------
576608
609+ @classmethod
610+ def action_register (cls , registers , inx , cell ):
611+ """Update with register number.
612+
613+ :meta private:
614+ """
615+ if cell .type == CELL_TYPE_BIT :
616+ registers [inx ].value = inx
617+ elif cell .type == CELL_TYPE_FLOAT32 :
618+ regs = cls .build_registers_from_value (float (inx ), False )
619+ registers [inx ].value = regs [0 ]
620+ registers [inx + 1 ].value = regs [1 ]
621+ elif cell .type == CELL_TYPE_UINT16 :
622+ registers [inx ].value = inx
623+ elif cell .type == CELL_TYPE_UINT32 :
624+ regs = cls .build_registers_from_value (inx , True )
625+ registers [inx ].value = regs [0 ]
626+ registers [inx + 1 ].value = regs [1 ]
627+
577628 @classmethod
578629 def action_random (cls , registers , inx , cell ):
579630 """Update with random value.
@@ -583,13 +634,15 @@ def action_random(cls, registers, inx, cell):
583634 if cell .type == CELL_TYPE_BIT :
584635 registers [inx ].value = random .randint (0 , 65536 )
585636 elif cell .type == CELL_TYPE_FLOAT32 :
586- regs = cls .build_registers_from_value (random .uniform (0.0 , 100 .0 ), False )
637+ regs = cls .build_registers_from_value (random .uniform (0.0 , 65000 .0 ), False )
587638 registers [inx ].value = regs [0 ]
588639 registers [inx + 1 ].value = regs [1 ]
589640 elif cell .type == CELL_TYPE_UINT16 :
590641 registers [inx ].value = random .randint (0 , 65536 )
591642 elif cell .type == CELL_TYPE_UINT32 :
592- regs = cls .build_registers_from_value (random .uniform (0.0 , 100.0 ), True )
643+ regs = cls .build_registers_from_value (
644+ int (random .uniform (0.0 , 65000.0 )), True
645+ )
593646 registers [inx ].value = regs [0 ]
594647 registers [inx + 1 ].value = regs [1 ]
595648
0 commit comments