diff --git a/doc/source/library/simulator/config.rst b/doc/source/library/simulator/config.rst index 700f9a89f..17f3376bb 100644 --- a/doc/source/library/simulator/config.rst +++ b/doc/source/library/simulator/config.rst @@ -331,19 +331,28 @@ Registers can be singulars (first entry) or arrays (second entry) Bits section ^^^^^^^^^^^^ -Example "bits" configuration: +Breaking change, bits have now been renamed into bitfield16, bitfield32 and bitfield64 to promote consistency + + + + +BitField16 section +^^^^^^^^^^^^^^ +Replaces "bits" + +Example "bitfield16" configuration: .. code-block:: - "bits": [ - 5, - [6, 7], + "bitfield16": [ + 6 + [6], {"addr": 8, "value": 7}, - {"addr": 9, "value": 7, "action": "random"}, - {"addr": [11, 12], "value": 7, "action": "random"} + {"addr": 9, "value": 3, "action": "increment"}, + {"addr": [11], "value": 1, "action": "random"} ], -defines registers which contain bits (discrete input and coils), +Defines registers which contain a 16 bit field, they are effectively sintactic sugar for Uint16 Registers can be singulars (first entry) or arrays (second entry), furthermore a value and/or a action can be defined, @@ -351,6 +360,50 @@ the value and/or action is inserted into each register defined in "addr". +BitField32 section +^^^^^^^^^^^^^^ + +Example "bitfield32" configuration: + +.. code-block:: + + "bitfield32": [ + [6, 7], + {"addr": [8, 9], "value": 31}, + {"addr": [10, 13], "value": 255, "action": "increment"}, + {"addr": [14, 15], "value": 65535, "action": "random"} + ], + +Defines registers which contain a 32 bit field, they are effectively sintactic sugar for Uint32 + +Registers can only be arrays in multiples of 2, +furthermore a value and/or a action can be defined, +the value and/or action is converted (high/low value) and inserted into each register set defined in "addr". + + +Uint64 section +^^^^^^^^^^^^^^ + +Example "uint64" configuration: + +.. code-block:: + + "uint64": [ + [6, 9], + {"addr": [8, 11], "value": 18446744073709551615}, + {"addr": [12, 15], "value": 255, "action": "increment"}, + {"addr": [16, 119], "value": 7, "action": "random"} + ], + +Defines registers which contain a 64 bit field, they are effectively sintactic sugar for Uint64 + +Registers can only be arrays in multiples of 4, +furthermore a value and/or a action can be defined, +the value and/or action is converted (high/low value) and inserted into each register set defined in "addr". + + + + Uint16 section ^^^^^^^^^^^^^^ @@ -395,6 +448,26 @@ furthermore a value and/or a action can be defined, the value and/or action is converted (high/low value) and inserted into each register set defined in "addr". +Uint64 section +^^^^^^^^^^^^^^ + +Example "uint64" configuration: + +.. code-block:: + + "uint64": [ + [6, 9], + {"addr": [8, 11], "value": 30012300}, + {"addr": [12, 15], "value": 40071200, "action": "increment"}, + {"addr": [16, 119], "value": 50051700, "action": "random"} + ], + +defines sets of registers (4) which contain a 64 bit unsigned integer, + +Registers can only be arrays in multiples of 4, +furthermore a value and/or a action can be defined, +the value and/or action is converted (high/low value) and inserted into each register set defined in "addr". + Float32 section ^^^^^^^^^^^^^^^ @@ -419,6 +492,29 @@ the value and/or action is converted (high/low value) and inserted into each reg Remark remember to set ``"value": `` like 512.0 (float) not 512 (integer). +Float64 section +^^^^^^^^^^^^^^^ + +Example "float64" configuration: + +.. code-block:: + + "float64": [ + [6, 9], + {"addr": [8, 11], "value": 3123.17}, + {"addr": [12, 15], "value": 712.5, "action": "increment"}, + {"addr": [16, 20], "value": 517.0, "action": "random"} + ], + +defines sets of registers (4) which contain a 64 bit float, + +Registers can only be arrays in multiples of 4, +furthermore a value and/or a action can be defined, +the value and/or action is converted (high/low value) and inserted into each register set defined in "addr". + +Remark remember to set ``"value": `` like 512.0 (float) not 512 (integer). + + String section ^^^^^^^^^^^^^^ diff --git a/examples/datastore_simulator_share.py b/examples/datastore_simulator_share.py index cc6eae19f..2971328ff 100755 --- a/examples/datastore_simulator_share.py +++ b/examples/datastore_simulator_share.py @@ -41,74 +41,131 @@ _logger = logging.getLogger(__file__) demo_config = { - "setup": { - "co size": 100, - "di size": 150, - "hr size": 200, - "ir size": 250, - "shared blocks": True, - "type exception": False, - "defaults": { - "value": { - "bits": 0x0708, - "uint16": 1, - "uint32": 45000, - "float32": 127.4, - "string": "X", - }, - "action": { - "bits": None, - "uint16": None, - "uint32": None, - "float32": None, - "string": None, + "setup": { + "co size": 100, + "di size": 150, + "hr size": 200, + "ir size": 300, + "shared blocks": True, + "type exception": False, + "defaults": { + "value": { + "bitfield16": 0x0708, + "bitfield32": 0x10010708, + "bitfield64": 0x8001000000003708, + "int16": -1, + "int32": -45000, + "int64": -450000000, + "uint16": 1, + "uint32": 45000, + "uint64": 450000000, + "float32": 127.4, + "float64": 10127.4, + "string": "X", + }, + "action": { + "bitfield16": None, + "bitfield32": None, + "bitfield64": None, + "int16": None, + "int32": None, + "int64": None, + "uint16": None, + "uint32": None, + "uint64": None, + "float32": None, + "float64": None, + "string": None, + }, }, }, - }, - "invalid": [ - 1, - [6, 6], - ], - "write": [ - 3, - [7, 8], - [16, 18], - [21, 26], - [31, 36], - ], - "bits": [ - [7, 9], - {"addr": 2, "value": 0x81}, - {"addr": 3, "value": 17}, - {"addr": 4, "value": 17}, - {"addr": 5, "value": 17}, - {"addr": 10, "value": 0x81}, - {"addr": [11, 12], "value": 0x04342}, - {"addr": 13, "action": "reset"}, - {"addr": 14, "value": 15, "action": "reset"}, - ], - "uint16": [ - {"addr": 16, "value": 3124}, - {"addr": [17, 18], "value": 5678}, - {"addr": [19, 20], "value": 14661, "action": "increment"}, - ], - "uint32": [ - {"addr": [21, 22], "value": 3124}, - {"addr": [23, 26], "value": 5678}, - {"addr": [27, 30], "value": 345000, "action": "increment"}, - ], - "float32": [ - {"addr": [31, 32], "value": 3124.17}, - {"addr": [33, 36], "value": 5678.19}, - {"addr": [37, 40], "value": 345000.18, "action": "increment"}, - ], - "string": [ - {"addr": [41, 42], "value": "Str"}, - {"addr": [43, 44], "value": "Strx"}, - ], - "repeat": [{"addr": [0, 45], "to": [46, 138]}], -} - + "invalid": [ + 1, + [3, 4], + ], + "write": [ + 5, + [7, 8], + [16, 18], + [21, 26], + [33, 38], + ], + "bitfield16": [ + [7, 7], + [8, 8], + {"addr": 2, "value": 0x81}, + {"addr": 3, "value": 17}, + {"addr": 4, "value": 17}, + {"addr": 5, "value": 17}, + {"addr": 10, "value": 0x81}, + {"addr": [11, 11], "value": 0x04342}, + {"addr": [12, 12], "value": 0x04342}, + {"addr": 13, "action": "random"}, + {"addr": 14, "value": 15, "action": "reset"}, + ], + "bitfield32": [ + [50, 51], + {"addr": [52,53], "value": 0x04342}, + ], + "bitfield64": [ + [54, 57], + {"addr": [58,61], "value": 0x04342}, + ], + "int16": [ + 70, + [71, 71], + {"addr": 72, "value": 0x81}, + {"addr": [73, 73], "value": 0x04342}, + {"addr": 74, "action": "random"}, + {"addr": 75, "value": 15, "action": "reset"}, + ], + "int32": [ + [76, 77], + {"addr": [78,79], "value": 0x04342}, + ], + "int64": [ + [80, 83], + {"addr": [84,87], "value": 0x04342}, + ], + "uint16": [ + {"addr": 16, "value": 3124}, + {"addr": [17, 18], "value": 5678}, + { + "addr": [19, 20], + "value": 14661, + "action": "increment", + "args": {"minval": 1, "maxval": 100}, + }, + ], + "uint32": [ + {"addr": [21, 22], "value": 3124}, + {"addr": [23, 26], "value": 5678}, + {"addr": [27, 30], "value": 345000, "action": "increment"}, + { + "addr": [31, 32], + "value": 50, + "action": "random", + "kwargs": {"minval": 10, "maxval": 80}, + }, + ], + "uint64": [ + {"addr": [62, 65], "value": 3124} + ], + "float32": [ + {"addr": [33, 34], "value": 3124.5}, + {"addr": [35, 38], "value": 5678.19}, + {"addr": [39, 42], "value": 345000.18, "action": "increment"}, + ], + "float64": [ + {"addr": [66, 69], "value": 3124.5}, + ], + "string": [ + {"addr": [43, 44], "value": "Str"}, + {"addr": [45, 48], "value": "Strxyz12"}, + ], + "repeat": [{"addr": [0, 95], "to": [96, 191]}, + {"addr": [0, 95], "to": [192, 287]}], + } def custom_action1(_inx, _cell): """Test action.""" diff --git a/examples/simulator.py b/examples/simulator.py index 58f7ce6fb..1549b6035 100755 --- a/examples/simulator.py +++ b/examples/simulator.py @@ -28,7 +28,7 @@ async def read_registers( if count == 1: value = rr.registers[0] else: - value = ModbusSimulatorContext.build_value_from_registers(rr.registers, is_int) + value = ModbusSimulatorContext.build_value_from_registers(rr.registers, is_int,4,False) if not is_int: value = round(value, 1) if curval: diff --git a/pymodbus/datastore/simulator.py b/pymodbus/datastore/simulator.py index 31b61d856..b3e48a820 100644 --- a/pymodbus/datastore/simulator.py +++ b/pymodbus/datastore/simulator.py @@ -19,12 +19,19 @@ class CellType: """Define single cell types.""" INVALID: int = 0 - BITS: int = 1 - UINT16: int = 2 - UINT32: int = 3 - FLOAT32: int = 4 - STRING: int = 5 - NEXT: int = 6 + BITFIELD16: int = 1 + BITFIELD32: int = 2 + BITFIELD64: int = 3 + INT16: int = 4 + INT32: int = 5 + INT64: int = 6 + UINT16: int = 7 + UINT32: int = 8 + UINT64: int = 9 + FLOAT32: int = 10 + FLOAT64: int = 11 + STRING: int = 12 + NEXT: int = 13 @dataclasses.dataclass(repr=False, eq=False) @@ -81,11 +88,18 @@ class Label: # pylint: disable=too-many-instance-attributes timestamp: str = "timestamp" repeat_to: str = "to" type: str = "type" - type_bits = "bits" + type_bitfield16 = "bitfield16" + type_bitfield32 = "bitfield32" + type_bitfield64 = "bitfield64" type_exception: str = "type exception" + type_int16: str = "int16" + type_int32: str = "int32" + type_int64: str = "int64" type_uint16: str = "uint16" type_uint32: str = "uint32" + type_uint64: str = "uint64" type_float32: str = "float32" + type_float64: str = "float64" type_string: str = "string" uptime: str = "uptime" value: str = "value" @@ -111,12 +125,47 @@ def __init__(self, runtime): self.runtime = runtime self.config = {} self.config_types: dict[str, dict[str, Any]] = { - Label.type_bits: { - Label.type: CellType.BITS, + Label.type_bitfield16: { + Label.type: CellType.BITFIELD16, Label.next: None, Label.value: 0, Label.action: None, - Label.method: self.handle_type_bits, + Label.method: self.handle_type_bitfield16, + }, + Label.type_bitfield32: { + Label.type: CellType.BITFIELD32, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_bitfield32, + }, + Label.type_bitfield64: { + Label.type: CellType.BITFIELD64, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_bitfield64, + }, + Label.type_int16: { + Label.type: CellType.INT16, + Label.next: None, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_int16, + }, + Label.type_int32: { + Label.type: CellType.INT32, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_int32, + }, + Label.type_int64: { + Label.type: CellType.INT64, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_int64, }, Label.type_uint16: { Label.type: CellType.UINT16, @@ -132,6 +181,13 @@ def __init__(self, runtime): Label.action: None, Label.method: self.handle_type_uint32, }, + Label.type_uint64: { + Label.type: CellType.UINT64, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_uint64, + }, Label.type_float32: { Label.type: CellType.FLOAT32, Label.next: CellType.NEXT, @@ -139,6 +195,13 @@ def __init__(self, runtime): Label.action: None, Label.method: self.handle_type_float32, }, + Label.type_float64: { + Label.type: CellType.FLOAT64, + Label.next: CellType.NEXT, + Label.value: 0, + Label.action: None, + Label.method: self.handle_type_float64, + }, Label.type_string: { Label.type: CellType.STRING, Label.next: CellType.NEXT, @@ -148,53 +211,99 @@ def __init__(self, runtime): }, } - def handle_type_bits(self, start, stop, value, action, action_kwargs): - """Handle type bits.""" - for reg in self.runtime.registers[start:stop]: - if reg.type != CellType.INVALID: - raise RuntimeError(f'ERROR "{Label.type_bits}" {reg} used') - reg.value = value - reg.type = CellType.BITS - reg.action = action - reg.action_kwargs = action_kwargs + def handle_type_2Bytes(self, start, stop, regs_value, action, action_kwargs,cell_type): + """Handle datatypes that require 2 bytes in order to be represented, e.g., 1 register.""" + for i in range(start, stop, 1): + regs = self.runtime.registers[i : i+1] + assert len(regs)==1,"Failed assertion" + if regs[0].type != CellType.INVALID : + raise RuntimeError(f'ERROR "{cell_type}" {i} is already being used') + + regs[0].action = action + regs[0].action_kwargs = action_kwargs + for ix in range (0,1): + regs[ix].value = regs_value[ix] + regs[ix].type = CellType.NEXT #Reg0 will be rewritten next + regs[0].type = cell_type + + def handle_type_4Bytes(self, start, stop, regs_value, action, action_kwargs,cell_type): + """Handle datatypes that require 4 bytes in order to be represented, e.g., 2 registers.""" + for i in range(start, stop, 2): + regs = self.runtime.registers[i : i + 2] + if regs[0].type != CellType.INVALID or regs[1].type != CellType.INVALID: + raise RuntimeError(f'ERROR "{cell_type}" {i},{i + 1} is already being used') + + regs[0].action = action + regs[0].action_kwargs = action_kwargs + for ix in range (0,2): + regs[ix].value = regs_value[ix] + regs[ix].type = CellType.NEXT #Reg0 will be rewritten next + regs[0].type = cell_type + + def handle_type_8Bytes(self, start, stop, regs_value, action, action_kwargs,cell_type): + """Handle datatypes that require 8 bytes in order to be represented, e.g., 4 registers.""" + for i in range(start, stop, 4): + regs = self.runtime.registers[i : i + 4] + if regs[0].type != CellType.INVALID or regs[1].type != CellType.INVALID or regs[2].type != CellType.INVALID or regs[3].type != CellType.INVALID: + raise RuntimeError(f'ERROR "{cell_type}" {i},{i + 1},{i + 2},{i + 3} is already being used') + regs[0].action = action + regs[0].action_kwargs = action_kwargs + for ix in range (0,4): + regs[ix].value = regs_value[ix] + regs[ix].type = CellType.NEXT #Reg0 will be rewritten next + regs[0].type = cell_type + def handle_type_bitfield16(self, start, stop, value, action, action_kwargs): + """Handle type bits16.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,2,False) + self.handle_type_2Bytes( start, stop, regs_value, action, action_kwargs,CellType.BITFIELD16) + def handle_type_bitfield32(self, start, stop, value, action, action_kwargs): + """Handle type bits32.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,4,False) + self.handle_type_4Bytes( start, stop, regs_value, action, action_kwargs,CellType.BITFIELD32) + def handle_type_bitfield64(self, start, stop, value, action, action_kwargs): + """Handle type bits64.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,8,False) + self.handle_type_8Bytes( start, stop, regs_value, action, action_kwargs,CellType.BITFIELD64) + + def handle_type_int16(self, start, stop, value, action, action_kwargs): + """Handle type int16.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,2,True) + self.handle_type_2Bytes( start, stop, regs_value, action, action_kwargs,CellType.INT16) + + def handle_type_int32(self, start, stop, value, action, action_kwargs): + """Handle type int32.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,4,True) + self.handle_type_4Bytes( start, stop, regs_value, action, action_kwargs,CellType.INT32) + + def handle_type_int64(self, start, stop, value, action, action_kwargs): + """Handle type int64.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,8,True) + self.handle_type_8Bytes( start, stop, regs_value, action, action_kwargs,CellType.INT64) def handle_type_uint16(self, start, stop, value, action, action_kwargs): """Handle type uint16.""" - for reg in self.runtime.registers[start:stop]: - if reg.type != CellType.INVALID: - raise RuntimeError(f'ERROR "{Label.type_uint16}" {reg} used') - reg.value = value - reg.type = CellType.UINT16 - reg.action = action - reg.action_kwargs = action_kwargs + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,2,False) + self.handle_type_2Bytes( start, stop, regs_value, action, action_kwargs,CellType.UINT16) def handle_type_uint32(self, start, stop, value, action, action_kwargs): """Handle type uint32.""" - regs_value = ModbusSimulatorContext.build_registers_from_value(value, True) - for i in range(start, stop, 2): - regs = self.runtime.registers[i : i + 2] - if regs[0].type != CellType.INVALID or regs[1].type != CellType.INVALID: - raise RuntimeError(f'ERROR "{Label.type_uint32}" {i},{i + 1} used') - regs[0].value = regs_value[0] - regs[0].type = CellType.UINT32 - regs[0].action = action - regs[0].action_kwargs = action_kwargs - regs[1].value = regs_value[1] - regs[1].type = CellType.NEXT + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,4,False) + self.handle_type_4Bytes( start, stop, regs_value, action, action_kwargs,CellType.UINT32) + + def handle_type_uint64(self, start, stop, value, action, action_kwargs): + """Handle type uint64.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, True,8,False) + self.handle_type_8Bytes( start, stop, regs_value, action, action_kwargs,CellType.UINT64) def handle_type_float32(self, start, stop, value, action, action_kwargs): """Handle type uint32.""" - regs_value = ModbusSimulatorContext.build_registers_from_value(value, False) - for i in range(start, stop, 2): - regs = self.runtime.registers[i : i + 2] - if regs[0].type != CellType.INVALID or regs[1].type != CellType.INVALID: - raise RuntimeError(f'ERROR "{Label.type_float32}" {i},{i + 1} used') - regs[0].value = regs_value[0] - regs[0].type = CellType.FLOAT32 - regs[0].action = action - regs[0].action_kwargs = action_kwargs - regs[1].value = regs_value[1] - regs[1].type = CellType.NEXT + regs_value = ModbusSimulatorContext.build_registers_from_value(value, False,4,None) + self.handle_type_4Bytes( start, stop, regs_value, action, action_kwargs,CellType.FLOAT32) + + def handle_type_float64(self, start, stop, value, action, action_kwargs): + """Handle type float64.""" + regs_value = ModbusSimulatorContext.build_registers_from_value(value, False,8,None) + self.handle_type_8Bytes( start, stop, regs_value, action, action_kwargs,CellType.FLOAT64) def handle_type_string(self, start, stop, value, action, action_kwargs): """Handle type string.""" @@ -208,7 +317,7 @@ def handle_type_string(self, start, stop, value, action, action_kwargs): for i in range(stop - start): reg = self.runtime.registers[start + i] if reg.type != CellType.INVALID: - raise RuntimeError(f'ERROR "{Label.type_string}" {start + i} used') + raise RuntimeError(f'ERROR "{Label.type_string}" at location {start + i} is already being used') j = i * 2 reg.value = int.from_bytes(bytes(value[j : j + 2], "UTF-8"), "big") reg.type = CellType.NEXT @@ -349,10 +458,17 @@ def setup(self, config, custom_actions) -> None: self.runtime.action_methods.append(method) i += 1 self.runtime.registerType_name_to_id = { - Label.type_bits: CellType.BITS, + Label.type_bitfield16: CellType.BITFIELD16, + Label.type_bitfield32: CellType.BITFIELD32, + Label.type_bitfield64: CellType.BITFIELD64, + Label.type_int16: CellType.INT16, + Label.type_int32: CellType.INT32, + Label.type_int64: CellType.INT64, Label.type_uint16: CellType.UINT16, Label.type_uint32: CellType.UINT32, + Label.type_uint64: CellType.UINT64, Label.type_float32: CellType.FLOAT32, + Label.type_float64: CellType.FLOAT64, Label.type_string: CellType.STRING, Label.next: CellType.NEXT, Label.invalid: CellType.INVALID, @@ -412,16 +528,29 @@ class ModbusSimulatorContext(ModbusBaseSlaveContext): "shared blocks": True, --> share memory for all blocks (largest size wins) "defaults": { "value": { --> Initial values (can be overwritten) - "bits": 0x01, + "bitfield16": 0x0001, -> e.g., a 16-bitmask + "bitfield32": 0x00010000,-> e.g., a 32-bitmask + "bitfield64": 0x0000000100000000,-> e.g., a 64-bitmask + "int16": -16, + "int32": -32, + "int64": -64, "uint16": 122, "uint32": 67000, + "uint64": 67000, "float32": 127.4, + "float64": 127.4, "string": " ", }, "action": { --> default action (can be overwritten) - "bits": None, + "bitfield16": None, + "bitfield32": None, + "bitfield64": None, + "int16": None, + "int32": None, + "int64": None, "uint16": None, "uint32": None, + "uint64": None, "float32": None, "string": None, }, @@ -435,21 +564,38 @@ class ModbusSimulatorContext(ModbusBaseSlaveContext): "write": [ --> allow write, efault is ReadOnly [5, 5] --> start, end bytes, repeated as needed ], - "bits": [ --> Define bits (1 register == 2 bytes) - [30, 31], --> start, end registers, repeated as needed - {"addr": [32, 34], "value": 0xF1}, --> with value - {"addr": [35, 36], "action": "increment"}, --> with action - {"addr": [37, 38], "action": "increment", "value": 0xF1} --> with action and value - {"addr": [37, 38], "action": "increment", "kwargs": {"min": 0, "max": 100}} --> with action with arguments + "bitfield16": [ --> Define bits (1 register == 2 bytes) + [30, 30], --> start, end registers, + {"addr": [32, 32], "value": 0xF1}, --> with value + {"addr": [35, 35], "action": "increment"}, --> with action + {"addr": [37, 37], "action": "increment", "value": 0xF1} --> with action and value + {"addr": [37, 37], "action": "increment", "kwargs": {"min": 0, "max": 100}} --> with action with arguments + ], + "int16": [ --> Define int16,signed (1 register == 2 bytes) + --> similar to bitfield16, but intended to represent a 16bit SIGNED number (-32768 <-> 32767) + ], + "int32": [ --> Define 32 bit signed integers (2 registers == 4 bytes) + --> similar to int16, but intended to represent a 32bit SIGNED number, occupies 2 registers + {'addr': [88, 89], 'value': 32} + ], + "int64": [ --> Define 64 bit signed integers (4 registers == 8 bytes) + --> similar to int32, uses 4 registers + {'addr': [88, 91], 'value': 64} ], "uint16": [ --> Define uint16 (1 register == 2 bytes) - --> same as type_bits + --> similar to int16, unsigned + ], + "uint32": [ --> Define 32 bit unsigned integers (2 registers == 4 bytes) + --> similar to int32, unsigned ], - "uint32": [ --> Define 32 bit integers (2 registers == 4 bytes) - --> same as type_bits + "uint64": [ --> Define 32 bit unsigned integers (4 registers == 8 bytes) + --> similar to int64, unsigned ], "float32": [ --> Define 32 bit floats (2 registers == 4 bytes) - --> same as type_bits + --> similar to int32, but encoding a floating number + ], + "float64": [ --> Define 64 bit floats (4 registers == 4 bytes) + --> similar to int32, but encoding a floating number ], "string": [ --> Define strings (variable number of registers (each 2 bytes)) [21, 22], --> start, end registers, define 1 string @@ -486,7 +632,7 @@ def __init__( # -------------------------------------------- # Simulator server interface # -------------------------------------------- - def get_text_register(self, register): + def get_text_register(self, register): # noqa: C901 """Get raw register.""" reg = self.registers[register] text_cell = TextCell() @@ -497,21 +643,48 @@ def get_text_register(self, register): text_cell.action = self.action_id_to_name[reg.action] if reg.action_kwargs: text_cell.action = f"{text_cell.action}({reg.action_kwargs})" - if reg.type in (CellType.INVALID, CellType.UINT16, CellType.NEXT): + if reg.type in (CellType.INVALID, CellType.NEXT): text_cell.value = str(reg.value) build_len = 0 - elif reg.type == CellType.BITS: - text_cell.value = hex(reg.value) + elif reg.type == CellType.BITFIELD16: + tmp_regs = [reg.value] + value=self.build_value_from_registers(tmp_regs, True,2,False) + text_cell.value ='0x' + hex(value)[2:].zfill(4) build_len = 0 - elif reg.type == CellType.UINT32: + elif reg.type == CellType.BITFIELD32: + tmp_regs = [reg.value, self.registers[register + 1].value] + value=self.build_value_from_registers(tmp_regs, True,4,False) + text_cell.value ='0x' + hex(value)[2:].zfill(8) + build_len = 2 + elif reg.type == CellType.BITFIELD64: + tmp_regs = [reg.value, self.registers[register + 1].value, self.registers[register + 2].value, self.registers[register + 3].value] + value=self.build_value_from_registers(tmp_regs, True,8,False) + text_cell.value ='0x' + hex(value)[2:].zfill(16) + build_len = 3 + elif reg.type in (CellType.UINT16,CellType.INT16): + tmp_regs = [reg.value, self.registers[register + 1].value] + is_signed= bool( reg.type ==CellType.INT16 ) + text_cell.value = str(self.build_value_from_registers(tmp_regs, True,2,is_signed)) + build_len = 1 + elif reg.type in (CellType.UINT32,CellType.INT32): tmp_regs = [reg.value, self.registers[register + 1].value] - text_cell.value = str(self.build_value_from_registers(tmp_regs, True)) + is_signed=bool( reg.type ==CellType.INT32 ) + text_cell.value = str(self.build_value_from_registers(tmp_regs, True,4,is_signed)) + build_len = 1 + elif reg.type in (CellType.UINT64,CellType.INT64): + tmp_regs = [reg.value, self.registers[register + 1].value,self.registers[register + 2].value, self.registers[register + 3].value] + is_signed=bool( reg.type ==CellType.INT64 ) + text_cell.value = str(self.build_value_from_registers(tmp_regs, True,8,is_signed)) build_len = 1 elif reg.type == CellType.FLOAT32: tmp_regs = [reg.value, self.registers[register + 1].value] - text_cell.value = str(self.build_value_from_registers(tmp_regs, False)) + text_cell.value = str(self.build_value_from_registers(tmp_regs, False,4,None)) build_len = 1 - else: # reg.type == CellType.STRING: + elif reg.type == CellType.FLOAT64: + tmp_regs = [reg.value, self.registers[register + 1].value, self.registers[register + 2].value, self.registers[register + 3].value] + text_cell.value = str(self.build_value_from_registers(tmp_regs, False,8,None)) + build_len = 3 + elif reg.type == CellType.STRING: j = register text_cell.value = "" while True: @@ -524,6 +697,10 @@ def get_text_register(self, register): if self.registers[j].type != CellType.NEXT: break build_len = j - register - 1 + else: + #Make sure all data types have a custom text generator, otherwise raise an error + raise RuntimeError('There is no text representation for the input type') + reg_txt = f"{register}-{register + build_len}" if build_len else f"{register}" return reg_txt, text_cell @@ -534,7 +711,7 @@ def get_text_register(self, register): _write_func_code = (5, 6, 15, 16, 22, 23) _bits_func_code = (1, 2, 5, 15) - def loop_validate(self, address, end_address, fx_write): + def loop_validate(self, address, end_address, fx_write): # noqa: C901 """Validate entry in loop. :meta private: @@ -549,17 +726,26 @@ def loop_validate(self, address, end_address, fx_write): continue if reg.type == CellType.NEXT: return False - if reg.type in (CellType.BITS, CellType.UINT16): + if reg.type in (CellType.BITFIELD16, CellType.UINT16,CellType.INT16): i += 1 - elif reg.type in (CellType.UINT32, CellType.FLOAT32): + elif reg.type in (CellType.BITFIELD32,CellType.UINT32,CellType.INT32, CellType.FLOAT32): if i + 1 >= end_address: return False i += 2 - else: + elif reg.type in (CellType.BITFIELD64,CellType.UINT64,CellType.INT64, CellType.FLOAT64): + if i + 3 >= end_address: + return False + i += 4 + elif reg.type == CellType.STRING: i += 1 while i < end_address: if self.registers[i].type == CellType.NEXT: i += 1 + else: + break + else: + raise RuntimeError('A cell without validation handler has been found') + return True def validate(self, func_code, address, count=1): @@ -653,58 +839,113 @@ def action_random(cls, registers, inx, cell, minval=1, maxval=65536): :meta private: """ - if cell.type in (CellType.BITS, CellType.UINT16): + if cell.type in (CellType.BITFIELD16, CellType.INT16, CellType.UINT16): + is_signed=bool( CellType.INT16) registers[inx].value = random.randint(int(minval), int(maxval)) + elif cell.type in (CellType.BITFIELD32, CellType.INT32, CellType.UINT32): + is_signed=bool( CellType.INT32) + regs = cls.build_registers_from_value( + random.randint(int(minval), int(maxval)),True,4,is_signed + ) + registers[inx].value = regs[0] + registers[inx + 1].value = regs[1] + elif cell.type in (CellType.BITFIELD64, CellType.INT64, CellType.UINT64): + is_signed=bool( CellType.INT64) + regs = cls.build_registers_from_value( + random.randint(int(minval), int(maxval)),True,8,is_signed + ) + registers[inx].value = regs[0] + registers[inx + 1].value = regs[1] + registers[inx + 2].value = regs[2] + registers[inx + 3].value = regs[3] elif cell.type == CellType.FLOAT32: regs = cls.build_registers_from_value( - random.uniform(float(minval), float(maxval)), False + random.uniform(float(minval), float(maxval)),False,4 ,None ) registers[inx].value = regs[0] registers[inx + 1].value = regs[1] - elif cell.type == CellType.UINT32: + elif cell.type == CellType.FLOAT64: regs = cls.build_registers_from_value( - random.randint(int(minval), int(maxval)), True + random.uniform(float(minval), float(maxval)),False,8, None ) registers[inx].value = regs[0] registers[inx + 1].value = regs[1] + registers[inx + 2].value = regs[2] + registers[inx + 3].value = regs[3] + @classmethod - def action_increment(cls, registers, inx, cell, minval=None, maxval=None): + def action_increment(cls, registers, inx, cell, minval=None, maxval=None): # noqa: C901 """Increment value reset with overflow. :meta private: """ reg = registers[inx] reg2 = registers[inx + 1] - if cell.type in (CellType.BITS, CellType.UINT16): - value = reg.value + 1 + reg3 = registers[inx + 2] + reg4 = registers[inx + 3] + + if cell.type in (CellType.BITFIELD16,CellType.INT16, CellType.UINT16): + is_signed=bool( CellType.INT16) + tmp_reg = [reg.value] + value = cls.build_value_from_registers(tmp_reg, True,2,is_signed) + value += 1 if maxval and value > maxval: value = minval if minval and value < minval: value = minval - reg.value = value + new_regs = cls.build_registers_from_value(value, True,2,is_signed) + reg.value = new_regs[0] + elif cell.type in (CellType.BITFIELD32,CellType.INT32, CellType.UINT32): + is_signed=bool( CellType.INT32) + tmp_reg = [reg.value, reg2.value] + value = cls.build_value_from_registers(tmp_reg, True,4,is_signed) + value += 1 + if maxval and value > maxval: + value = minval + if minval and value < minval: + value = minval + new_regs = cls.build_registers_from_value(value, True,4,is_signed) + reg.value = new_regs[0] + reg2.value = new_regs[1] + elif cell.type in (CellType.BITFIELD64,CellType.INT64, CellType.UINT64): + is_signed=bool( CellType.INT64) + tmp_reg = [reg.value, reg2.value, reg3.value, reg4.value] + value = cls.build_value_from_registers(tmp_reg, True,8,is_signed) + value += 1 + if maxval and value > maxval: + value = minval + if minval and value < minval: + value = minval + new_regs = cls.build_registers_from_value(value, True,8,is_signed) + reg.value = new_regs[0] + reg2.value = new_regs[1] + reg3.value = new_regs[2] + reg4.value = new_regs[3] elif cell.type == CellType.FLOAT32: tmp_reg = [reg.value, reg2.value] - value = cls.build_value_from_registers(tmp_reg, False) + value = cls.build_value_from_registers(tmp_reg, False,4,None) value += 1.0 if maxval and value > maxval: value = minval if minval and value < minval: value = minval - new_regs = cls.build_registers_from_value(value, False) + new_regs = cls.build_registers_from_value(value, False,4,None) reg.value = new_regs[0] reg2.value = new_regs[1] - elif cell.type == CellType.UINT32: - tmp_reg = [reg.value, reg2.value] - value = cls.build_value_from_registers(tmp_reg, True) - value += 1 + elif cell.type == CellType.FLOAT64: + tmp_reg = [reg.value, reg2.value, reg3.value, reg4.value] + value = cls.build_value_from_registers(tmp_reg, False,8,None) + value += 1.0 if maxval and value > maxval: value = minval if minval and value < minval: value = minval - new_regs = cls.build_registers_from_value(value, True) + new_regs = cls.build_registers_from_value(value, False,8,None) reg.value = new_regs[0] reg2.value = new_regs[1] + reg3.value = new_regs[2] + reg4.value = new_regs[3] @classmethod def action_timestamp(cls, registers, inx, _cell, **_kwargs): @@ -737,17 +978,24 @@ def action_uptime(cls, registers, inx, cell, **_kwargs): """ value = int(datetime.now().timestamp()) - cls.start_time + 1 - if cell.type in (CellType.BITS, CellType.UINT16): + if cell.type in (CellType.BITFIELD16, CellType.UINT16): registers[inx].value = value elif cell.type == CellType.FLOAT32: - regs = cls.build_registers_from_value(value, False) + regs = cls.build_registers_from_value(value, False,4,None) registers[inx].value = regs[0] registers[inx + 1].value = regs[1] elif cell.type == CellType.UINT32: - regs = cls.build_registers_from_value(value, True) + regs = cls.build_registers_from_value(value, True,4,False) + registers[inx].value = regs[0] + registers[inx + 1].value = regs[1] + elif cell.type == CellType.FLOAT64: + regs = cls.build_registers_from_value(value, False,8,None) + registers[inx].value = regs[0] + registers[inx + 1].value = regs[1] + elif cell.type == CellType.UINT64: + regs = cls.build_registers_from_value(value, True,8,False) registers[inx].value = regs[0] registers[inx + 1].value = regs[1] - # -------------------------------------------- # Internal helper methods # -------------------------------------------- @@ -760,16 +1008,18 @@ def validate_type(self, func_code, real_address, count) -> bool: check: tuple if func_code in self._bits_func_code: # Bit access - check = (CellType.BITS, -1) + check = (CellType.BITFIELD16, -1) reg_step = 1 elif count % 2: # 16 bit access - check = (CellType.UINT16, CellType.STRING) + check = (CellType.BITFIELD16,CellType.INT16,CellType.UINT16, CellType.STRING) reg_step = 1 - else: - check = (CellType.UINT32, CellType.FLOAT32, CellType.STRING) + elif count % 4: + check = (CellType.BITFIELD32,CellType.INT32,CellType.UINT32, CellType.FLOAT32, CellType.STRING) reg_step = 2 - + elif count % 8: + check = (CellType.BITFIELD64,CellType.INT64,CellType.UINT64, CellType.FLOAT64) + reg_step = 4 for i in range(real_address, real_address + count, reg_step): if self.registers[i].type in check: continue @@ -779,25 +1029,69 @@ def validate_type(self, func_code, real_address, count) -> bool: return True @classmethod - def build_registers_from_value(cls, value, is_int): + def build_registers_from_value(cls, value, is_int,n_bytes,is_signed): # noqa: C901 """Build registers from int32 or float32.""" - regs = [0, 0] if is_int: - value_bytes = int.to_bytes(value, 4, "big") + if n_bytes==2 and is_signed is False: + value_bytes = struct.pack(">H", value)#int.to_bytes(value, 4, "big") + if n_bytes==2 and is_signed is True: + value_bytes = struct.pack(">h", value)#int.to_bytes(value, 4, "big") + if n_bytes==4 and is_signed is False: + value_bytes = struct.pack(">I", value)#int.to_bytes(value, 4, "big") + if n_bytes==4 and is_signed is True: + value_bytes = struct.pack(">i", value)#int.to_bytes(value, 4, "big") + if n_bytes==8 and is_signed is False: + value_bytes = struct.pack(">Q", value)#int.to_bytes(value, 4, "big") + if n_bytes==8 and is_signed is True: + value_bytes = struct.pack(">q", value)#int.to_bytes(value, 4, "big") else: - value_bytes = struct.pack(">f", value) - regs[0] = int.from_bytes(value_bytes[:2], "big") - regs[1] = int.from_bytes(value_bytes[-2:], "big") + if n_bytes==4: + value_bytes = struct.pack(">f", value) + if n_bytes==8: + value_bytes = struct.pack(">d", value)#int.to_bytes(value, 4, "big") + + if n_bytes==2: + regs = [0] + regs[0] = int.from_bytes(value_bytes[:2], "big") + if n_bytes==4: + regs = [0, 0] + regs[0] = int.from_bytes(value_bytes[:2], "big") + regs[1] = int.from_bytes(value_bytes[-2:], "big") + if n_bytes==8: + regs = [0, 0,0,0] + regs[0] = int.from_bytes(value_bytes[:2], "big") + regs[1] = int.from_bytes(value_bytes[2:4], "big") + regs[2] = int.from_bytes(value_bytes[4:6], "big") + regs[3] = int.from_bytes(value_bytes[6:8], "big") + return regs @classmethod - def build_value_from_registers(cls, registers, is_int): - """Build int32 or float32 value from registers.""" - value_bytes = int.to_bytes(registers[0], 2, "big") + int.to_bytes( - registers[1], 2, "big" - ) + def build_value_from_registers(cls, registers, is_int,n_bytes,is_signed): # noqa: C901 + """Build int16,int32,int64 or float32 value from registers.""" + if n_bytes==2: + value_bytes = int.to_bytes(registers[0], 2, "big") + if n_bytes==4: + value_bytes = int.to_bytes(registers[0], 2, "big") + int.to_bytes(registers[1], 2, "big") + if n_bytes==8: + value_bytes = int.to_bytes(registers[0], 2, "big") + int.to_bytes(registers[1], 2, "big") + int.to_bytes( registers[2], 2, "big" )+ int.to_bytes(registers[3], 2, "big" ) + if is_int: - value = int.from_bytes(value_bytes, "big") + if n_bytes==2 and is_signed is False: + value = struct.unpack(">H", value_bytes) + if n_bytes==2 and is_signed is True: + value = struct.unpack(">h", value_bytes) + if n_bytes==4 and is_signed is False: + value = struct.unpack(">I", value_bytes) + if n_bytes==4 and is_signed is True: + value = struct.unpack(">i", value_bytes) + if n_bytes==8 and is_signed is False: + value = struct.unpack(">Q", value_bytes) + if n_bytes==8 and is_signed is True: + value = struct.unpack(">q", value_bytes) else: - value = struct.unpack(">f", value_bytes)[0] - return value + if n_bytes==4: + value = struct.unpack(">f", value_bytes) + if n_bytes==8: + value = struct.unpack(">d", value_bytes) + return value[0] diff --git a/pymodbus/server/simulator/setup.json b/pymodbus/server/simulator/setup.json index ea3213b62..9b83e1f92 100644 --- a/pymodbus/server/simulator/setup.json +++ b/pymodbus/server/simulator/setup.json @@ -78,17 +78,31 @@ "type exception": true, "defaults": { "value": { - "bits": 0, + "bitfield16": 0, + "bitfield32": 0, + "bitfield64": 0, + "int16": 0, + "int32": 0, + "int64": 0, "uint16": 0, "uint32": 0, + "uint64": 0, "float32": 0.0, + "float64": 0.0, "string": " " }, "action": { - "bits": null, + "bitfield16": null, + "bitfield32": null, + "bitfield64": null, + "int16": "increment", + "int32": "increment", + "int64": "increment", "uint16": "increment", "uint32": "increment", + "uint64": "increment", "float32": "increment", + "float64": "increment", "string": null } } @@ -99,21 +113,38 @@ "write": [ 3 ], - "bits": [ + "bitfield16": [ {"addr": 2, "value": 7} ], + "bitfield32": [ + {"addr": [28, 29], "value": 31, "action": null} + ], + "bitfield64": [ + {"addr": [30, 33], "value": 63, "action": null} + ], + "int16": [ + {"addr": [27, 27], "value": -17001, "action": "increment"} + ], + "int32": [ + {"addr": [25, 26], "value": -617001, "action": "increment"} + ], + "int64": [ + {"addr": [21, 24], "value": -64000000, "action": "increment"} + ], "uint16": [ - {"addr": 3, "value": 17001, "action": null}, + {"addr": 3, "value": 17001, "action": "random"}, 2100 ], "uint32": [ - {"addr": [4, 5], "value": 617001, "action": null}, + {"addr": [4, 5], "value": 617001, "action": "random"}, [3037, 3038] ], + "uint64":[{"addr": [12, 15], "value": 64000000, "action": "random"}], "float32": [ {"addr": [6, 7], "value": 404.17}, [4100, 4101] ], + "float64":[{"addr": [8, 11], "value": 64.64}], "string": [ 5047, {"addr": [16, 20], "value": "A_B_C_D_E_"} @@ -131,17 +162,31 @@ "type exception": true, "defaults": { "value": { - "bits": 0, + "bitfield16": 0, + "bitfield32": 0, + "bitfield64": 0, + "int16": 0, + "int32": 0, + "int64": 0, "uint16": 0, "uint32": 0, + "uint64": 0, "float32": 0.0, + "float64": 0.0, "string": " " }, "action": { - "bits": null, + "bitfield16": null, + "bitfield32": null, + "bitfield64": null, + "int16": null, + "int32": null, + "int64": null, "uint16": null, "uint32": null, + "uint64": null, "float32": null, + "float64": null, "string": null } } @@ -153,7 +198,7 @@ "write": [ 10 ], - "bits": [ + "bitfield16": [ 10, 1009, [1116, 1119], @@ -161,6 +206,16 @@ {"addr": [1148,1149], "value": 32117}, {"addr": [1208, 1306], "action": "random"} ], + "bitfield32": [ + ], + "bitfield64": [ + ], + "int16": [ + ], + "int32": [ + ], + "int64": [ + ], "uint16": [ 11, 2027, @@ -198,6 +253,7 @@ "kwargs": {"minval": 45000, "maxval": 55000} } ], + "uint64":[], "float32": [ [14, 15], [4047, 4048], @@ -217,6 +273,7 @@ "kwargs": {"minval": 45000.0, "maxval": 55000.0} } ], + "float64":[], "string": [ {"addr": [16, 20], "value": "A_B_C_D_E_"}, {"addr": [529, 544], "value": "Brand name, 32 bytes...........X"} @@ -225,4 +282,4 @@ ] } } -} +} \ No newline at end of file diff --git a/test/sub_server/test_simulator.py b/test/sub_server/test_simulator.py index aeefed8b7..59ddfd95b 100644 --- a/test/sub_server/test_simulator.py +++ b/test/sub_server/test_simulator.py @@ -28,22 +28,36 @@ class TestSimulator: "co size": 100, "di size": 150, "hr size": 200, - "ir size": 250, + "ir size": 300, "shared blocks": True, "type exception": False, "defaults": { "value": { - "bits": 0x0708, + "bitfield16": 0x0708, + "bitfield32": 0x10010708, + "bitfield64": 0x8001000000003708, + "int16": -1, + "int32": -45000, + "int64": -450000000, "uint16": 1, "uint32": 45000, + "uint64": 450000000, "float32": 127.4, + "float64": 10127.4, "string": "X", }, "action": { - "bits": None, + "bitfield16": None, + "bitfield32": None, + "bitfield64": None, + "int16": None, + "int32": None, + "int64": None, "uint16": None, "uint32": None, + "uint64": None, "float32": None, + "float64": None, "string": None, }, }, @@ -59,14 +73,40 @@ class TestSimulator: [21, 26], [33, 38], ], - "bits": [ + "bitfield16": [ 5, - [7, 8], + [7, 7], + [8, 8], {"addr": 10, "value": 0x81}, - {"addr": [11, 12], "value": 0x04342}, + {"addr": [11, 11], "value": 0x04342}, + {"addr": [12, 12], "value": 0x04342}, {"addr": 13, "action": "random"}, {"addr": 14, "value": 15, "action": "reset"}, ], + "bitfield32": [ + [50, 51], + {"addr": [52,53], "value": 0x04342}, + ], + "bitfield64": [ + [54, 57], + {"addr": [58,61], "value": 0x04342}, + ], + "int16": [ + 70, + [71, 71], + {"addr": 72, "value": 0x81}, + {"addr": [73, 73], "value": 0x04342}, + {"addr": 74, "action": "random"}, + {"addr": 75, "value": 15, "action": "reset"}, + ], + "int32": [ + [76, 77], + {"addr": [78,79], "value": 0x04342}, + ], + "int64": [ + [80, 83], + {"addr": [84,87], "value": 0x04342}, + ], "uint16": [ {"addr": 16, "value": 3124}, {"addr": [17, 18], "value": 5678}, @@ -88,16 +128,23 @@ class TestSimulator: "kwargs": {"minval": 10, "maxval": 80}, }, ], + "uint64": [ + {"addr": [62, 65], "value": 3124} + ], "float32": [ {"addr": [33, 34], "value": 3124.5}, {"addr": [35, 38], "value": 5678.19}, {"addr": [39, 42], "value": 345000.18, "action": "increment"}, ], + "float64": [ + {"addr": [66, 69], "value": 3124.5}, + ], "string": [ {"addr": [43, 44], "value": "Str"}, {"addr": [45, 48], "value": "Strxyz12"}, ], - "repeat": [{"addr": [0, 48], "to": [49, 147]}], + "repeat": [{"addr": [0, 95], "to": [96, 191]}, + {"addr": [0, 95], "to": [192, 287]}], } default_server_config = { @@ -124,16 +171,16 @@ class TestSimulator: Cell(), Cell(), Cell(), - Cell(type=CellType.BITS, access=True, value=0x0708), + Cell(type=CellType.BITFIELD16, access=True, value=0x0708), Cell(type=CellType.INVALID), - Cell(type=CellType.BITS, access=True, value=0x0708), - Cell(type=CellType.BITS, access=True, value=0x0708), + Cell(type=CellType.BITFIELD16, access=True, value=0x0708), + Cell(type=CellType.BITFIELD16, access=True, value=0x0708), Cell(type=CellType.INVALID), - Cell(type=CellType.BITS, value=0x81), # 10 - Cell(type=CellType.BITS, value=0x4342), - Cell(type=CellType.BITS, value=0x4342), - Cell(type=CellType.BITS, value=1800, action=2), - Cell(type=CellType.BITS, value=15, action=3), + Cell(type=CellType.BITFIELD16, value=0x81), # 10 + Cell(type=CellType.BITFIELD16, value=0x4342), + Cell(type=CellType.BITFIELD16, value=0x4342), + Cell(type=CellType.BITFIELD16, value=1800, action=2), + Cell(type=CellType.BITFIELD16, value=15, action=3), Cell(type=CellType.INVALID), Cell(type=CellType.UINT16, access=True, value=3124), Cell(type=CellType.UINT16, access=True, value=5678), @@ -194,20 +241,20 @@ def setup_method(self): def test_pack_unpack_values(self): """Test the pack unpack methods.""" value = 32145678 - regs = ModbusSimulatorContext.build_registers_from_value(value, True) - test_value = ModbusSimulatorContext.build_value_from_registers(regs, True) + regs = ModbusSimulatorContext.build_registers_from_value(value, True,4,False) + test_value = ModbusSimulatorContext.build_value_from_registers(regs, True,4,False) assert value == test_value value = 3.14159265358979 - regs = ModbusSimulatorContext.build_registers_from_value(value, False) - test_value = ModbusSimulatorContext.build_value_from_registers(regs, False) + regs = ModbusSimulatorContext.build_registers_from_value(value, False,4,None) + test_value = ModbusSimulatorContext.build_value_from_registers(regs, False,4,None) assert round(value, 6) == round(test_value, 6) def test_simulator_config_verify(self): """Test basic configuration.""" # Manually build expected memory image and then compare. - assert self.simulator.register_count == 250 - for offset in (0, 49, 98): + assert self.simulator.register_count == 300 + for offset in (0, 96, 192): for i, test_cell in enumerate(self.test_registers): reg = self.simulator.registers[i + offset] assert reg.type == test_cell.type, f"at index {i} - {offset}" @@ -229,14 +276,14 @@ def test_simulator_config_verify2(self): # Manually build expected memory image and then compare. exc_setup = copy.deepcopy(self.default_config) exc_setup[Label.setup][Label.shared_blocks] = False - exc_setup[Label.setup][Label.co_size] = 15 - exc_setup[Label.setup][Label.di_size] = 15 - exc_setup[Label.setup][Label.hr_size] = 15 - exc_setup[Label.setup][Label.ir_size] = 15 + exc_setup[Label.setup][Label.co_size] = 150 + exc_setup[Label.setup][Label.di_size] = 150 + exc_setup[Label.setup][Label.hr_size] = 150 + exc_setup[Label.setup][Label.ir_size] = 150 del exc_setup[Label.repeat] exc_setup[Label.repeat] = [] simulator = ModbusSimulatorContext(exc_setup, None) - assert simulator.register_count == 60 + assert simulator.register_count == 600 for i, test_cell in enumerate(self.test_registers): reg = simulator.registers[i] assert reg.type == test_cell.type, f"at index {i}" @@ -249,7 +296,7 @@ def test_simulator_invalid_config(self): with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) for entry in ( - (Label.type_bits, 5), + (Label.type_bitfield16, 5), (Label.type_uint16, 16), (Label.type_uint32, [31, 32]), (Label.type_float32, [33, 34]), @@ -260,7 +307,7 @@ def test_simulator_invalid_config(self): with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) - del exc_setup[Label.type_bits] + del exc_setup[Label.type_bitfield16] with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) @@ -269,7 +316,7 @@ def test_simulator_invalid_config(self): ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) exc_setup[Label.setup][Label.defaults][Label.action][ - Label.type_bits + Label.type_bitfield16 ] = "bad action" with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) @@ -286,7 +333,7 @@ def test_simulator_invalid_config(self): with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) - exc_setup[Label.type_bits].append(700) + exc_setup[Label.type_bitfield16].append(700) with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) @@ -297,7 +344,7 @@ def test_simulator_invalid_config(self): exc_setup[Label.type_uint16].append(0) ModbusSimulatorContext(exc_setup, None) exc_setup = copy.deepcopy(self.default_config) - exc_setup[Label.type_uint16].append(250) + exc_setup[Label.type_uint16].append(350) with pytest.raises(RuntimeError): ModbusSimulatorContext(exc_setup, None) @@ -414,7 +461,7 @@ def test_simulator_get_text(self): """Test get_text_register().""" for test_reg, test_entry, test_cell in ( (1, "1", Cell(type=Label.invalid, action="none", value="0")), - (5, "5", Cell(type=Label.type_bits, action="none", value="0x708")), + (5, "5", Cell(type=Label.type_bitfield16, action="none", value="0x0708")), ( 31, "31-32", @@ -492,8 +539,8 @@ def test_simulator_action_reset(self): @pytest.mark.parametrize( ("celltype", "minval", "maxval", "value", "expected"), [ - (CellType.BITS, 50, 75, 73, (74, 75, 50)), - (CellType.BITS, 50, 75, 45, (50, 51, 52)), + (CellType.BITFIELD16, 50, 75, 73, (74, 75, 50)), + (CellType.BITFIELD16, 50, 75, 45, (50, 51, 52)), (CellType.UINT16, 50, 15075, 15073, (15074, 15075, 50)), (CellType.UINT16, 50, 75, 45, (50, 51, 52)), (CellType.UINT32, 50, 63075, 63073, (63074, 63075, 50)), @@ -518,17 +565,22 @@ def test_simulator_action_increment( exc_simulator.registers[30].action_kwargs = kwargs exc_simulator.registers[31].type = CellType.NEXT - is_int = celltype != CellType.FLOAT32 - reg_count = 1 if celltype in (CellType.BITS, CellType.UINT16) else 2 + is_int = False if celltype in (CellType.FLOAT32,CellType.FLOAT64) else True + reg_count = 1 if celltype in (CellType.BITFIELD16, CellType.UINT16) else 2 + n_bytes=2 + if celltype in (CellType.BITFIELD32, CellType.INT32, CellType.UINT32, CellType.FLOAT32): + n_bytes=4 + if celltype in (CellType.BITFIELD64, CellType.INT64, CellType.UINT64, CellType.FLOAT64): + n_bytes=8 regs = ( [value, 0] if reg_count == 1 - else ModbusSimulatorContext.build_registers_from_value(value, is_int) + else ModbusSimulatorContext.build_registers_from_value(value, is_int,n_bytes,False) ) exc_simulator.registers[30].value = regs[0] exc_simulator.registers[31].value = regs[1] for expect_value in expected: - if celltype != CellType.BITS: + if celltype != CellType.BITFIELD16: regs = exc_simulator.getValues(FX_READ_REG, 30, reg_count) else: reg_bits = exc_simulator.getValues(FX_READ_BIT, 30 * 16, 16) @@ -538,14 +590,14 @@ def test_simulator_action_increment( assert expect_value == regs[0], f"type({celltype})" else: new_value = ModbusSimulatorContext.build_value_from_registers( - regs, is_int + regs, is_int,n_bytes,False ) assert expect_value == new_value, f"type({celltype})" @pytest.mark.parametrize( ("celltype", "minval", "maxval"), [ - (CellType.BITS, 50, 75), + (CellType.BITFIELD16, 50, 75), (CellType.UINT16, 50, 15075), (CellType.UINT32, 50, 63075), (CellType.FLOAT32, 27.0, 16100.5), @@ -565,10 +617,15 @@ def test_simulator_action_random(self, celltype, minval, maxval): exc_simulator.registers[30].action = action exc_simulator.registers[30].action_kwargs = kwargs exc_simulator.registers[31].type = CellType.NEXT - is_int = celltype != CellType.FLOAT32 - reg_count = 1 if celltype in (CellType.BITS, CellType.UINT16) else 2 + is_int = False if celltype in (CellType.FLOAT32,CellType.FLOAT64) else True + n_bytes=2 + if celltype in (CellType.BITFIELD32, CellType.INT32, CellType.UINT32, CellType.FLOAT32): + n_bytes=4 + if celltype in (CellType.BITFIELD64, CellType.INT64, CellType.UINT64, CellType.FLOAT64): + n_bytes=8 + reg_count = 1 if celltype in (CellType.BITFIELD16, CellType.UINT16) else 2 for _i in range(100): - if celltype != CellType.BITS: + if celltype != CellType.BITFIELD16: regs = exc_simulator.getValues(FX_READ_REG, 30, reg_count) else: reg_bits = exc_simulator.getValues(FX_READ_BIT, 30 * 16, 16) @@ -578,7 +635,7 @@ def test_simulator_action_random(self, celltype, minval, maxval): new_value = regs[0] else: new_value = ModbusSimulatorContext.build_value_from_registers( - regs, is_int + regs, is_int,n_bytes,False ) assert minval <= new_value <= maxval @@ -599,3 +656,4 @@ async def test_simulator_server_tcp(self, unused_tcp_port): await task.run_forever(only_start=True) await asyncio.sleep(0.5) await task.stop() +