Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New simulator datastore. #2535

Merged
merged 4 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ Please select a topic in the left hand column.
source/client
source/server
source/repl
source/simulator3
source/simulator
source/examples
source/authors
source/changelog
source/internals
source/roadmap
.. include:: ../README.rst

.. include:: ../README.rst
Binary file modified doc/source/_static/examples.tgz
Binary file not shown.
Binary file modified doc/source/_static/examples.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion doc/source/library/simulator/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ each containing a list of servers/devices
}

You can define as many server and devices as you like, when starting
:ref:`pymodbus.simulator` you select one server and one device to simulate.
:ref:`pymodbus.simulator (v3.x)` you select one server and one device to simulate.

A entry in “device_list” correspond to the dict you can use as parameter
to datastore_simulator is you want to construct your own simulator.
Expand Down
8 changes: 4 additions & 4 deletions doc/source/library/simulator/web.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Web frontend
============
Web frontend v3.x
=================

TO BE DOCUMENTED.



pymodbus.simulator
------------------
pymodbus.simulator (v3.x)
-------------------------

The easiest way to run the simulator with web is to use "pymodbus.simulator" from the commandline.

Expand Down
100 changes: 82 additions & 18 deletions doc/source/simulator.rst
Original file line number Diff line number Diff line change
@@ -1,40 +1,104 @@
Simulator
=========

The simulator is a full fledged modbus simulator, which is
constantly being evolved with user ideas / amendments.
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 datastore simulator allows the user to (all automated)
The simulator allows the user to (all automated):

- simulate a modbus device by adding a simple configuration,
- test how a client handles modbus exceptions,
- 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 allows the user to (online / manual)
The web interface (activated optionally) allows the user to:

- test how a client handles modbus errors,
- test how a client handles communication errors like divided messages,
- run your test server in the cloud,
- introduce modbus errors (like e.g. wrong length),
- introduce communication errors (like splitting a message),
- monitor requests/responses,
- inject modbus errors like malicious a response,
- 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 with unix domain sockets in your test harness,
- spin up a test server in your test harness,
- set expected responses with a simple REST API command,
- check the result with another simple REST API command,
- check the result with a simple REST API command,
- test your client app in a true end-to-end fashion.

.. toctree::
:maxdepth: 4
:hidden:
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:`SimActions` is a helper class that defines built in actions.

: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:


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
--------

library/simulator/config
library/simulator/datastore
library/simulator/web
library/simulator/restapi
.. note:: This is a v4.0.0 functionality currently not available, please see the 3x simulator server.
42 changes: 42 additions & 0 deletions doc/source/simulator3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Simulator (3.x)
===============

.. warning:: Beginning with v3.9.0 and ending with v4.0.0 this simulator will be replaced by a new version.

The simulator is a full fledged modbus simulator, which is
constantly being evolved with user ideas / amendments.

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 datastore simulator allows the user to (all automated)

- simulate a modbus device by adding a simple configuration,
- test how a client handles modbus exceptions,
- test a client apps correct use of the simulated device.

The web interface allows the user to (online / manual)

- test how a client handles modbus errors,
- test how a client handles communication errors like divided messages,
- run your test server in the cloud,
- monitor requests/responses,
- inject modbus errors like malicious a response,
- see/Change values online.

The REST API allow the test process to be automated

- spin up a test server with unix domain sockets in your test harness,
- set expected responses with a simple REST API command,
- check the result with another simple REST API command,
- test your client app in a true end-to-end fashion.

.. toctree::
:maxdepth: 4
:hidden:

library/simulator/config
library/simulator/datastore
library/simulator/web
library/simulator/restapi
69 changes: 69 additions & 0 deletions examples/simulator_datamodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Pymodbus simulator datamodel examples.

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()
20 changes: 10 additions & 10 deletions pymodbus/server/startstop.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)


async def StartAsyncTcpServer( # pylint: disable=invalid-name
async def StartAsyncTcpServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs,
Expand All @@ -35,7 +35,7 @@ async def StartAsyncTcpServer( # pylint: disable=invalid-name
await ModbusTcpServer(context, custom_pdu=custom_functions, **kwargs).serve_forever()


def StartTcpServer( # pylint: disable=invalid-name
def StartTcpServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs
Expand All @@ -54,7 +54,7 @@ def StartTcpServer( # pylint: disable=invalid-name
asyncio.run(StartAsyncTcpServer(context, custom_functions=custom_functions, **kwargs))


async def StartAsyncTlsServer( # pylint: disable=invalid-name
async def StartAsyncTlsServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs,
Expand All @@ -73,7 +73,7 @@ async def StartAsyncTlsServer( # pylint: disable=invalid-name
await ModbusTlsServer(context, custom_pdu=custom_functions, **kwargs).serve_forever()


def StartTlsServer( # pylint: disable=invalid-name
def StartTlsServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs
Expand All @@ -92,7 +92,7 @@ def StartTlsServer( # pylint: disable=invalid-name
asyncio.run(StartAsyncTlsServer(context, custom_functions=custom_functions, **kwargs))


async def StartAsyncUdpServer( # pylint: disable=invalid-name
async def StartAsyncUdpServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs,
Expand All @@ -111,7 +111,7 @@ async def StartAsyncUdpServer( # pylint: disable=invalid-name
await ModbusUdpServer(context, custom_pdu=custom_functions, **kwargs).serve_forever()


def StartUdpServer( # pylint: disable=invalid-name
def StartUdpServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs
Expand All @@ -130,7 +130,7 @@ def StartUdpServer( # pylint: disable=invalid-name
asyncio.run(StartAsyncUdpServer(context, custom_functions=custom_functions, **kwargs))


async def StartAsyncSerialServer( # pylint: disable=invalid-name
async def StartAsyncSerialServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs,
Expand All @@ -149,7 +149,7 @@ async def StartAsyncSerialServer( # pylint: disable=invalid-name
await ModbusSerialServer(context, custom_pdu=custom_functions, **kwargs).serve_forever()


def StartSerialServer( # pylint: disable=invalid-name
def StartSerialServer(
context: ModbusServerContext,
custom_functions: list[type[ModbusPDU]] | None = None,
**kwargs
Expand All @@ -168,15 +168,15 @@ def StartSerialServer( # pylint: disable=invalid-name
asyncio.run(StartAsyncSerialServer(context, custom_functions=custom_functions, **kwargs))


async def ServerAsyncStop() -> None: # pylint: disable=invalid-name
async def ServerAsyncStop() -> None:
"""Terminate server."""
if not ModbusBaseServer.active_server:
raise RuntimeError("Modbus server not running.")
await ModbusBaseServer.active_server.shutdown()
ModbusBaseServer.active_server = None


def ServerStop() -> None: # pylint: disable=invalid-name
def ServerStop() -> None:
"""Terminate server."""
if not ModbusBaseServer.active_server:
raise RuntimeError("Modbus server not running.")
Expand Down
10 changes: 10 additions & 0 deletions pymodbus/simulator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Simulator."""

__all__ = [
"SimCheckConfig",
"SimData",
"SimDataType",
"SimDevice"
]

from pymodbus.simulator.simdata import SimCheckConfig, SimData, SimDataType, SimDevice
6 changes: 6 additions & 0 deletions pymodbus/simulator/simcore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Simulator data model classes."""
from __future__ import annotations


class SimCore: # pylint: disable=too-few-public-methods
"""Datastore for the simulator/server."""
Loading
Loading