diff --git a/doc/index.rst b/doc/index.rst index 78c64cc6f..fb6471f2c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -14,7 +14,7 @@ Please select a topic in the left hand column. source/server source/repl source/simulator3 - source/simulator + source/simulator/simulator source/examples source/authors source/changelog diff --git a/doc/source/_static/examples.tgz b/doc/source/_static/examples.tgz index 3c1f328df..3c19fc2fc 100644 Binary files a/doc/source/_static/examples.tgz and b/doc/source/_static/examples.tgz differ diff --git a/doc/source/_static/examples.zip b/doc/source/_static/examples.zip index dbe97d851..129bf8efa 100644 Binary files a/doc/source/_static/examples.zip and b/doc/source/_static/examples.zip differ diff --git a/doc/source/library/pymodbus.rst b/doc/source/library/pymodbus.rst index aeb419ad1..bc6a28ee9 100644 --- a/doc/source/library/pymodbus.rst +++ b/doc/source/library/pymodbus.rst @@ -6,16 +6,6 @@ Extra functions :undoc-members: :show-inheritance: -.. automodule:: pymodbus.device - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: pymodbus.events - :members: - :undoc-members: - :show-inheritance: - .. automodule:: pymodbus.exceptions :members: :undoc-members: @@ -75,3 +65,13 @@ PDU classes :members: :undoc-members: :show-inheritance: + +.. automodule:: pymodbus.pdu.device + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pymodbus.pdu.events + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/server.rst b/doc/source/server.rst index 980828d66..42c0e8c9c 100644 --- a/doc/source/server.rst +++ b/doc/source/server.rst @@ -1,5 +1,5 @@ -Server -====== +Server (3.x) +============ Pymodbus offers servers with transport protocols for diff --git a/doc/source/simulator.rst b/doc/source/simulator.rst deleted file mode 100644 index a11755969..000000000 --- a/doc/source/simulator.rst +++ /dev/null @@ -1,114 +0,0 @@ -Simulator -========= - -**WORK IN PROGRESS, do NOT use** - -The simulator is a full fledged modbus server/simulator. - -The purpose of the simulator is to provide support for client -application test harnesses with end-to-end testing simulating real life -modbus devices. - -The simulator allows the user to (all automated): - -- simulate a modbus device by adding a simple configuration, -- simulate a multipoint line, but adding multiple device configurations, -- simulate devices that are not conforming to the protocol, -- simulate communication problems (data loss etc), -- test how a client handles modbus response and exceptions, -- test a client apps correct use of the simulated device. - -The web interface (activated optionally) allows the user to: - -- introduce modbus errors (like e.g. wrong length), -- introduce communication errors (like splitting a message), -- monitor requests/responses, -- see/Change values online. -- inject modbus errors like malicious a response, -- run your test server in the cloud, - -The REST API allow the test process to be automated - -- spin up a test server in your test harness, -- set expected responses with a simple REST API command, -- check the result with a simple REST API command, -- test your client app in a true end-to-end fashion. - -The web server uses the REST API internally, which helps to ensure that it -actually works. - - -Data model configuration ------------------------- - -.. warning:: from v3.9.0 this is available as a "normal" datastore model. - -The simulator data model represent the registers and parameters of the simulated devices. -The data model is defined using :class:`SimData` and :class:`SimDevice` before starting the -server and cannot be changed without restarting the server. - -:class:`SimData` defines a group of continuous identical registers. This is the basis of the model, -multiple :class:`SimData` should be used to mirror the physical device. - -:class:`SimDevice` defines device parameters and a list of :class:`SimData`. -The list of :class:`SimData` can added as shared registers or as the 4 blocks, defined in modbus. -:class:`SimDevice` can be used to simulate a single device, while a list of -:class:`SimDevice` simulates a multipoint line (simulating a rs485 line or a tcp based serial forwarder). - -A server consist of communication parameters and a device or a list of devices - -:class:`SimDataType` is a helper class that defines legal datatypes. - -:class:`SimValueType` is a helper class that defines legal value types. - -:github:`examples/simulator_datamodel.py` contains usage examples. - -SimData -^^^^^^^ - -.. autoclass:: pymodbus.simulator.SimData - :members: - :undoc-members: - :show-inheritance: - -SimDevice -^^^^^^^^^ - -.. autoclass:: pymodbus.simulator.SimDevice - :members: - :undoc-members: - :show-inheritance: - -SimDataType -^^^^^^^^^^^ - -.. autoclass:: pymodbus.simulator.SimDataType - :members: - :undoc-members: - :show-inheritance: - -SimDataValue -^^^^^^^^^^^^ - -.. autoclass:: pymodbus.simulator.SimDataValue - :members: - :undoc-members: - :show-inheritance: - - -Simulator server ----------------- - -.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. - - -Web frontend ------------- - -.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. - - -REST API --------- - -.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. diff --git a/doc/source/simulator/datamodel.rst b/doc/source/simulator/datamodel.rst new file mode 100644 index 000000000..848adef25 --- /dev/null +++ b/doc/source/simulator/datamodel.rst @@ -0,0 +1,54 @@ +Data model configuration +------------------------ +The simulator data model represent the registers and parameters of the simulated devices. +The data model is defined using :class:`SimData` and :class:`SimDevice` before starting the +server and cannot be changed without restarting the server. + +:class:`SimData` defines a group of continuous identical registers. This is the basis of the model, +multiple :class:`SimData` are used to mirror the physical device. + +:class:`SimDevice` defines device parameters and a list of :class:`SimData`. The +list of :class:`SimData` can be added as shared registers or as 4 separate blocks as defined in modbus. +:class:`SimDevice` are used to simulate a single device, while a list of +:class:`SimDevice` simulates a multipoint line (rs485 line) or a serial forwarder. + +A server consist of communication parameters and a list of :class:`SimDevice` + + +Usage examples +^^^^^^^^^^^^^^ +.. literalinclude:: ../../../examples/server_datamodel.py + :language: python + + +Class definitions +^^^^^^^^^^^^^^^^^ +.. autoclass:: pymodbus.simulator.SimData + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + +.. autoclass:: pymodbus.simulator.SimDevice + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + +.. autoclass:: pymodbus.simulator.SimDataType + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + +.. autoclass:: pymodbus.simulator.SimValueType + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + +.. autoclass:: pymodbus.simulator.SimAction + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/doc/source/simulator/restapi.rst b/doc/source/simulator/restapi.rst new file mode 100644 index 000000000..87d68efaf --- /dev/null +++ b/doc/source/simulator/restapi.rst @@ -0,0 +1,4 @@ +REST API +-------- + +.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. diff --git a/doc/source/simulator/server.rst b/doc/source/simulator/server.rst new file mode 100644 index 000000000..ba4925058 --- /dev/null +++ b/doc/source/simulator/server.rst @@ -0,0 +1,4 @@ +Simulator server +---------------- + +.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. diff --git a/doc/source/simulator/simulator.rst b/doc/source/simulator/simulator.rst new file mode 100644 index 000000000..30bd783a3 --- /dev/null +++ b/doc/source/simulator/simulator.rst @@ -0,0 +1,49 @@ +Server/Simulator +================ + +**WORK IN PROGRESS, do NOT use** + +The simulator is a full fledged modbus server/simulator. + +The purpose of the simulator is to provide support for client +application test harnesses with end-to-end testing simulating real life +modbus devices. + +The simulator allows the user to (all automated): + +- simulate a modbus device by adding a simple configuration, +- simulate a multipoint line, but adding multiple device configurations, +- simulate devices that are not conforming to the protocol, +- simulate communication problems (data loss etc), +- test how a client handles modbus response and exceptions, +- test a client apps correct use of the simulated device. + +The web interface (activated optionally) allows the user to: + +- introduce modbus errors (like e.g. wrong length), +- introduce communication errors (like splitting a message), +- monitor requests/responses, +- see/Change values online. +- inject modbus errors like malicious a response, +- run your test server in the cloud, + +The REST API allow the test process to be automated + +- spin up a test server in your test harness, +- set expected responses with a simple REST API command, +- check the result with a simple REST API command, +- test your client app in a true end-to-end fashion. + +The web server uses the REST API internally, which helps to ensure that it +actually works. + + +.. toctree:: + :maxdepth: 8 + :caption: Contents: + :hidden: + + ./datamodel + ./server + ./web + ./restapi diff --git a/doc/source/simulator/web.rst b/doc/source/simulator/web.rst new file mode 100644 index 000000000..797078242 --- /dev/null +++ b/doc/source/simulator/web.rst @@ -0,0 +1,4 @@ +Web frontend +------------ + +.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server. diff --git a/doc/source/upgrade_40.rst b/doc/source/upgrade_40.rst index 5036a1299..8f12dc84a 100644 --- a/doc/source/upgrade_40.rst +++ b/doc/source/upgrade_40.rst @@ -27,15 +27,17 @@ Please replace by result.convert_from_registers() and/or convert_to_registers() Simple replacements ------------------- -please replace +please replace parameters as follows + - slave= with device_id= - slaves= with device_ids= - ModbusServerContext(slaves=) with ModbusServerContext(devices=) -please rename +please rename classes/methods as follows + - ModbusSlaveContext to ModbusDeviceContext - RemoteSlaveContext to RemoteDeviceContext -- report_slave_id() with report_device_id() +- report_slave_id to report_device_id - diag_read_slave_message_count with diag_read_device_message_count - diag_read_slave_no_response_count with diag_read_device_no_response_count - diag_read_slave_nak_count with diag_read_device_nak_count diff --git a/examples/server_datamodel.py b/examples/server_datamodel.py new file mode 100755 index 000000000..0f91a0b6f --- /dev/null +++ b/examples/server_datamodel.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Pymodbus server datamodel examples. + +This file shows examples of how to configure the datamodel for the server/simulator. + +There are different examples showing the flexibility of the datamodel. +""" + +from pymodbus.simulator import SimData, SimDataType, SimDevice + + +def define_datamodel(): + """Define register groups. + + Coils and direct inputs are modeled as bits representing a relay in the device. + There are no real difference between coils and direct inputs, but historically + they have been divided. Please be aware the coils and direct inputs are addressed differently + in shared vs non-shared models. + - In a non-shared model the address is the bit directly. + It can be thought of as if 1 register == 1 bit. + - In a shared model the address is the register containing the bits. + 1 register == 16bit, so a single bit CANNOT be addressed directly. + + Holding registers and input registers are modeled as int/float/string representing a sensor in the device. + There are no real difference between holding registers and input registers, but historically they have + been divided. + Please be aware that 1 sensor might be modeled as several register because it needs more than + 16 bit for accuracy (e.g. a INT32). + """ + # SimData can be instantiated with positional or optional parameters: + assert SimData( + 5, 17, 10, SimDataType.REGISTERS + ) == SimData( + address=5, value=17, count=10, datatype=SimDataType.REGISTERS + ) + + # Define a group of coils/direct inputs non-shared (address=15..31 each 1 bit) + block1 = SimData(address=15, value=True, count=16, datatype=SimDataType.BITS) + # Define a group of coils/direct inputs shared (address=15..31 each 16 bit) + block2 = SimData(address=15, value=0xFFFF, count=16, datatype=SimDataType.BITS) + + # Define a group of holding/input registers (remark NO difference between shared and non-shared) + block3 = SimData(10, 123.4, datatype=SimDataType.FLOAT32) + block4 = SimData(17, value=123, count=5, datatype=SimDataType.INT64) + block5 = SimData(27, "Hello ", datatype=SimDataType.STRING) + + # Please use SimDataType.DEFAULT to define register limits. + # this datatype only uses 1 object, whereas SimDataType.REGISTERS uses objects, + # mean SimDataType.DEFAULT is factors more efficient and much less memory consuming + block_def = SimData(0, count=1000, datatype=SimDataType.DEFAULT) + + # SimDevice can be instantiated with positional or optional parameters: + assert SimDevice( + 5,False, [block_def, block5] + ) == SimDevice( + id=5, type_check=False, block_shared=[block_def, block5] + ) + + # SimDevice can define either a shared or a non-shared register model + SimDevice(1, False, block_shared=[block_def, block5]) + SimDevice(2, False, + block_coil=[block1], + block_direct=[block1], + block_holding=[block2], + block_input=[block3, block4]) + # Remark: it is legal to reuse SimData, the object is only used for configuration, + # not for runtime. + + # id=0 in a SimDevice act as a "catch all". Requests to an unknown id is executed in this SimDevice. + SimDevice(0, block_shared=[block2]) + + +def main(): + """Combine setup and run.""" + define_datamodel() + +if __name__ == "__main__": + main() diff --git a/examples/simulator_datamodel.py b/examples/simulator_datamodel.py deleted file mode 100755 index 9015db72a..000000000 --- a/examples/simulator_datamodel.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -"""Pymodbus simulator datamodel examples. - -**WORK IN PROGRESS, do NOT use** - -This example shows how to configure the simulator datamodel to mimic a real -device. - -There are different examples, to show the flexibility of the simulator datamodel. - -.. tip:: This is NOT the pymodbus simulator, that is started as pymodbus.simulator. -""" - -from pymodbus.simulator import SimCheckConfig, SimData, SimDataType, SimDevice - - -def define_registers(): - """Define simulator data model. - - Coils and direct inputs are expressed as bits representing a relay in the device. - There are no real difference between coils and direct inputs, but historically - they have been divided. - - Holding registers and input registers are the same, but historically they have - been divided. - - Coils and direct inputs are handled differently in shared vs non-shared models. - - - In a non-shared model the address is the bit directly. It can be thought of as if a - register only contains 1 bit. - - In a shared model the address is the register containing the bits. So a single bit CANNOT - be addressed directly. - """ - # Define a group of coils (remark difference between shared and non-shared) - block_coil = [SimData(0, count=100, datatype=SimDataType.DEFAULT), - SimData(0, True, 16)] - block_coil_shared = [SimData(0, 0xFFFF, 16)] - - # SimData can be reused with copying - block_direct = block_coil - - # Define a group of registers (remark NO difference between shared and non-shared) - block_holding = [SimData(10, count=100, datatype=SimDataType.DEFAULT), - SimData(10, 123.4, datatype=SimDataType.FLOAT32), - SimData(12, 123456789.3, datatype=SimDataType.FLOAT64), - SimData(17, value=123, count=5, datatype=SimDataType.INT32), - SimData(27, "Hello ", datatype=SimDataType.STRING)] - block_input = block_holding - block_shared = [SimData(10, 123.4, datatype=SimDataType.FLOAT32), - SimData(12, 123456789.3, datatype=SimDataType.FLOAT64), - SimData(16, 0xf0f0, datatype=SimDataType.BITS), - SimData(17, value=123, count=5, datatype=SimDataType.INT32), - SimData(27, "Hello ", datatype=SimDataType.STRING)] - - device_block = SimDevice(1, False, - block_coil=block_coil, - block_direct=block_direct, - block_holding=block_holding, - block_input=block_input) - device_shared = SimDevice(2, False, - block_shared=block_coil_shared+block_shared) - assert not SimCheckConfig([device_block]) - assert not SimCheckConfig([device_shared]) - assert not SimCheckConfig([device_shared, device_block]) - -def main(): - """Combine setup and run.""" - define_registers() - -if __name__ == "__main__": - main() diff --git a/pymodbus/simulator/__init__.py b/pymodbus/simulator/__init__.py index ffff3a21a..cefbc4403 100644 --- a/pymodbus/simulator/__init__.py +++ b/pymodbus/simulator/__init__.py @@ -1,15 +1,17 @@ """Simulator.""" __all__ = [ - "SimCheckConfig", + "SimAction", + "SimCore", "SimData", "SimDataType", "SimDevice", "SimValueType", ] +from pymodbus.simulator.simcore import SimCore from pymodbus.simulator.simdata import ( - SimCheckConfig, + SimAction, SimData, SimDataType, SimDevice, diff --git a/pymodbus/simulator/simcore.py b/pymodbus/simulator/simcore.py index 7ec5ef57a..ed7ebbfa3 100644 --- a/pymodbus/simulator/simcore.py +++ b/pymodbus/simulator/simcore.py @@ -1,6 +1,44 @@ -"""Simulator data model classes.""" +"""Simulator data model implementation.""" from __future__ import annotations +from .simdata import SimData, SimDevice -class SimCore: # pylint: disable=too-few-public-methods + +class SimCore: """Datastore for the simulator/server.""" + + def __init__(self) -> None: + """Build datastore.""" + self.devices: dict[int, SimDevice] = {} + + @classmethod + def build_block(cls, _block: list[SimData]) -> tuple[int, int, int] | None: + """Build registers for device.""" + return None + + @classmethod + def build_config(cls, devices: list[SimDevice]) -> SimCore: + """Build devices/registers ready for server/simulator.""" + core = SimCore() + for dev in devices: + if dev.id in core.devices: + raise TypeError(f"device id {dev.id} defined multiple times.") + block_coil = block_direct = block_holding = block_input = block_shared = None + for cfg_block, _block in ( + (dev.block_coil, block_coil), + (dev.block_direct, block_direct), + (dev.block_holding, block_holding), + (dev.block_input, block_input), + (dev.block_shared, block_shared) + ): + if cfg_block: + cls.build_block(cfg_block) + + #core.devices[dev.id] = ( + # block_coil, + # block_direct, + # block_holding, + # block_input, + # block_shared + #) + return core diff --git a/pymodbus/simulator/simdata.py b/pymodbus/simulator/simdata.py index 3721355eb..a1e7e0aa6 100644 --- a/pymodbus/simulator/simdata.py +++ b/pymodbus/simulator/simdata.py @@ -1,16 +1,18 @@ """Simulator data model classes.""" from __future__ import annotations -from collections.abc import Callable +import asyncio +from collections.abc import Awaitable, Callable from dataclasses import dataclass from enum import Enum from typing import TypeAlias SimValueType: TypeAlias = int | float | str | bool | bytes +SimAction: TypeAlias = Callable[[SimValueType], SimValueType] | Awaitable[SimValueType] class SimDataType(Enum): - """Register types, used to define group of registers. + """Register types, used to type of a group of registers. This is the types pymodbus recognizes, actually the modbus standard do NOT define e.g. INT32, but since nearly every device contain e.g. values of type INT32, it is available in pymodbus, @@ -41,11 +43,11 @@ class SimDataType(Enum): BITS = 10 #: Raw registers #: - #: .. warning:: Do not use as default, since it fills the memory and block other registrations. + #: .. warning:: Do not use as default because it fills the memory and block other registrations. REGISTERS = 11 - #: Defube register address limits. + #: Define register address limits and default values #: - #: .. tip:: It a single but special register, and therefore improves speed and memory usage compared to REGISTERS. + #: .. tip:: Implemented a single but special register, and therefore improves speed and memory usage compared to REGISTERS. DEFAULT = 12 @dataclass(frozen=True) @@ -65,7 +67,7 @@ class SimData: The above code defines 5 INT32, each with the value -123456, in total 10 registers (address 100-109) - .. code-block:: python + .. code-block:: python SimData( address=100, @@ -118,8 +120,6 @@ class SimData: """ #: Address of first register, starting with 0. - #: - #: .. caution:: No default, must be defined. address: int #: Value of datatype, to initialize the registers (repeated with count, apart from string). @@ -152,8 +152,41 @@ class SimData: #: value: SimValueType) -> SimValueType: #: return value + 1 #: + #: async def my_action( + #: addr: int, + #: value: SimValueType) -> SimValueType: + #: return value + 1 + #: #: .. tip:: use functools.partial to add extra parameters if needed. - action: Callable[[int, SimValueType], SimValueType] | None = None + action: SimAction | None = None + + def __check_datatype(self): + """Check datatype.""" + if self.datatype == SimDataType.STRING and not isinstance(self.value, str): + raise TypeError("SimDataType.STRING but value not a string") + if self.datatype in ( + SimDataType.INT16, + SimDataType.UINT16, + SimDataType.INT32, + SimDataType.UINT32, + SimDataType.INT64, + SimDataType.UINT64, + ) and not isinstance(self.value, int): + raise TypeError("SimDataType.INT variant but value not a int") + if self.datatype in ( + SimDataType.FLOAT32, + SimDataType.FLOAT64, + ) and not isinstance(self.value, float): + raise TypeError("SimDataType.FLOAT variant but value not a float") + if self.datatype == SimDataType.BITS and not isinstance(self.value, (bool, int)): + raise TypeError("SimDataType.BITS but value not a bool or int") + if self.datatype == SimDataType.REGISTERS and not isinstance(self.value, int): + raise TypeError("SimDataType.REGISTERS but value not a int") + if self.datatype == SimDataType.DEFAULT: + if self.action: + raise TypeError("SimDataType.DEFAULT cannot have an action") + if not isinstance(self.value, int): + raise TypeError("SimDataType.DEFAULT but value not a int") def __post_init__(self): """Define a group of registers.""" @@ -161,13 +194,13 @@ def __post_init__(self): raise TypeError("0 <= address < 65535") if not isinstance(self.count, int) or not 0 < self.count <= 65535: raise TypeError("0 < count <= 65535") + if self.action and not (callable(self.action) or asyncio.iscoroutinefunction(self.action)): + raise TypeError("action not Callable or Awaitable (async)") if not isinstance(self.datatype, SimDataType): raise TypeError("datatype not SimDataType") - if self.action and not callable(self.action): - raise TypeError("action not Callable") if not isinstance(self.value, SimValueType): raise TypeError("value not a supported type") - + self.__check_datatype() @dataclass(frozen=True) class SimDevice: @@ -281,9 +314,3 @@ def __post_init__(self): for entry in block: if not isinstance(entry, SimData): raise TypeError(f"block_{name} contains non SimData entries") - - -def SimCheckConfig(devices: list[SimDevice]) -> bool: - """Verify configuration.""" - _ = devices - return False diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py index 19f687ae3..0d7bc2488 100755 --- a/test/examples/test_examples.py +++ b/test/examples/test_examples.py @@ -20,12 +20,12 @@ from examples.message_parser import main as main_parse_messages from examples.server_async import setup_server from examples.server_callback import run_callback_server +from examples.server_datamodel import main as run_main_datamodel from examples.server_sync import run_sync_server from examples.server_updating import main as main_updating_server from examples.simple_async_client import run_async_simple_client from examples.simple_sync_client import run_sync_simple_client from examples.simulator import run_simulator as run_simulator3 -from examples.simulator_datamodel import main as run_main_simulator_datamodel from pymodbus.exceptions import ModbusException from pymodbus.pdu import ExceptionResponse from pymodbus.server import ServerAsyncStop, ServerStop @@ -94,9 +94,9 @@ async def test_simulator3(self): # Awaiting fix, missing stop of task. await run_simulator3() - def test_simulator_datamodel(self): + def test_server_datamodel(self): """Run different simulator configurations.""" - run_main_simulator_datamodel() + run_main_datamodel() async def test_modbus_forwarder(self): """Test modbus forwarder.""" diff --git a/test/simulator/test_simcore.py b/test/simulator/test_simcore.py new file mode 100644 index 000000000..b29cf791c --- /dev/null +++ b/test/simulator/test_simcore.py @@ -0,0 +1,12 @@ +"""Test SimCore.""" + + +from pymodbus.simulator import SimCore + + +class TestSimCore: + """Test simulator data config.""" + + def test_instanciate(self): + """Test that simdata can be objects.""" + SimCore() diff --git a/test/simulator/test_simdata.py b/test/simulator/test_simdata.py index 7368d7468..94ac5b634 100644 --- a/test/simulator/test_simdata.py +++ b/test/simulator/test_simdata.py @@ -1,8 +1,8 @@ -"""Test pdu.""" +"""Test SimData.""" import pytest -from pymodbus.simulator import SimData, SimDataType, SimDevice +from pymodbus.simulator import SimData, SimDataType class TestSimData: @@ -10,37 +10,66 @@ class TestSimData: def test_instanciate(self): """Test that simdata can be objects.""" - a = SimData(0) - SimDevice(0, block_shared=[a]) + SimData(0) @pytest.mark.parametrize("address", ["not ok", 1.0, -1, 70000]) def test_simdata_address(self, address): - """Test that simdata can be objects.""" + """Test simdata address.""" with pytest.raises(TypeError): SimData(address) SimData(0) @pytest.mark.parametrize("count", ["not ok", 1.0, -1, 70000]) def test_simdata_count(self, count): - """Test that simdata can be objects.""" + """Test simdata count.""" with pytest.raises(TypeError): SimData(address=0, count=count) SimData(0, count=2) @pytest.mark.parametrize("datatype", ["not ok", 1.0, 11]) def test_simdata_datatype(self, datatype): - """Test that simdata can be objects.""" + """Test simdata datatype.""" with pytest.raises(TypeError): SimData(0, datatype=datatype) SimData(0, datatype=SimDataType.BITS) - @pytest.mark.parametrize("action", ["my action"]) - def test_simdata_action(self, action): - """Test that simdata can be objects.""" + @pytest.mark.parametrize(("value", "value_type"), [ + ("ok str", SimDataType.STRING), + (1.0, SimDataType.FLOAT32), + (11, SimDataType.REGISTERS), + (True, SimDataType.BITS), + ]) + def test_simdata_value_ok(self, value, value_type): + """Test simdata value.""" + SimData(0, value=value, datatype=value_type) + + @pytest.mark.parametrize(("value", "value_type"), [ + ([True, False], SimDataType.BITS), + ({0: 1}, SimDataType.REGISTERS), + ((1, 0), SimDataType.REGISTERS), + (123, SimDataType.STRING), + ("", SimDataType.INT16), + (123, SimDataType.FLOAT32), + (123.0, SimDataType.BITS), + (123.0, SimDataType.REGISTERS), + ("", SimDataType.DEFAULT), + ]) + def test_simdata_value_not_ok(self, value, value_type): + """Test simdata value.""" + with pytest.raises(TypeError): + SimData(0, value=value, datatype=value_type) + + def test_simdata_action(self): + """Test simdata action.""" def dummy_action(): """Set action.""" + async def async_dummy_action(): + """Set action.""" + with pytest.raises(TypeError): - SimData(0, action=action) + SimData(0, action="not_ok") SimData(0, action=dummy_action) - + SimData(0, action=async_dummy_action) + with pytest.raises(TypeError): + SimData(0, datatype=SimDataType.DEFAULT, action=dummy_action) diff --git a/test/simulator/test_simdevice.py b/test/simulator/test_simdevice.py index a6231b3b1..21288404d 100644 --- a/test/simulator/test_simdevice.py +++ b/test/simulator/test_simdevice.py @@ -1,4 +1,4 @@ -"""Test pdu.""" +"""Test SimDevice.""" import pytest