Skip to content

Commit

Permalink
Modbus simulator - Added support for additional datatypes
Browse files Browse the repository at this point in the history
This update adds native support for some of the most commonly found
datatypes in OT systems. This includes support for bitfields, unsigned
integers, signed integers, and floating point numbers in multiple word
sizes (16 bit, 32 bit and 64 bit).

Relevant changes include:
A reworked version of simulator.py  that relies on struct.unpack,
struct.pack, and other native functions to simplifly datatype handling
while also incorportating additional checks to ensure the register layout
behaves as expected.
In order to simplifly future work development, the Bits field has been
renamed bitfield16, while also adding support for 32 and 64 bit fields.
To help facilitate adoption, several changes have been done to the project
files to ensure a smooth transition. This includes:
	- Updated documentation to reflect new datatypes, including examples.
	- Updated unit tests, to ensure the code works as expected.
	- Updated the simulator example to illustrate how the new datatypes
	  can be used
	- Performed linting on 3.12

Resolves [discussion]: pymodbus-dev#1458
Partially addresses: pymodbus-dev#1284
  • Loading branch information
pnl-jseb committed May 22, 2024
1 parent a0e3044 commit 740878b
Show file tree
Hide file tree
Showing 6 changed files with 801 additions and 239 deletions.
110 changes: 103 additions & 7 deletions doc/source/library/simulator/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,26 +331,79 @@ 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,
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
^^^^^^^^^^^^^^

Expand Down Expand Up @@ -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
^^^^^^^^^^^^^^^
Expand All @@ -419,6 +492,29 @@ the value and/or action is converted (high/low value) and inserted into each reg
Remark remember to set ``"value": <float 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": <float value>`` like 512.0 (float) not 512 (integer).



String section
^^^^^^^^^^^^^^
Expand Down
189 changes: 123 additions & 66 deletions examples/datastore_simulator_share.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion examples/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 740878b

Please sign in to comment.