Skip to content

Commit

Permalink
Simulator datastore.
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen committed Jan 7, 2025
1 parent 901a87d commit d6abf15
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 18 deletions.
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.
58 changes: 50 additions & 8 deletions doc/source/simulator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,53 @@ The REST API allow the test process to be automated
- 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
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`

:class:`SimData` defines a group of continuous identical registers.

:class:`SimDevice` defines device parameters and a list of :class:`SimData` defining
shared/block registers.

A server consist of communication parameters and 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:

SimActions
^^^^^^^^^^

.. autoclass:: pymodbus.simulator.SimActions
:members:
:undoc-members:
:show-inheritance:
40 changes: 40 additions & 0 deletions doc/source/simulator3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Simulator (3.x)
===============

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
94 changes: 94 additions & 0 deletions examples/simulator_datamodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Pymodbus simulator server/client Example.
An example of how to use the simulator (server) with a client.
for usage see documentation of simulator
.. tip:: pymodbus.simulator starts the server directly from the commandline
"""
import asyncio
import logging

from pymodbus import FramerType
from pymodbus.client import AsyncModbusTcpClient
from pymodbus.datastore import ModbusSimulatorContext
from pymodbus.server import ModbusSimulatorServer, get_simulator_commandline


_logger = logging.getLogger(__file__)


async def read_registers(
client, addr, count, is_int, curval=None, minval=None, maxval=None
):
"""Run modbus call."""
rr = await client.read_holding_registers(addr, count=count, slave=1)
assert not rr.isError()
if count == 1:
value = rr.registers[0]
else:
value = ModbusSimulatorContext.build_value_from_registers(rr.registers, is_int)
if not is_int:
value = round(value, 1)
if curval:
assert value == curval, f"{value} == {curval}"
else:
assert minval <= value <= maxval, f"{minval} <= {value} <= {maxval}"


async def run_calls(client, count):
"""Run client calls."""
_logger.info("### Read fixed/increment/random value of different types.")
_logger.info("--> UINT16")
for count in range(1, 5):
await read_registers(client, 1148, 1, True, curval=32117)
await read_registers(client, 2305, 1, True, curval=50 + count)
await read_registers(client, 2306, 1, True, minval=45, maxval=55)

_logger.info("--> UINT32")
await read_registers(client, 3188, 2, True, curval=32514)
await read_registers(client, 3876, 2, True, curval=50000 + count)
await read_registers(client, 3878, 2, True, minval=45000, maxval=55000)

_logger.info("--> FLOAT32")
await read_registers(client, 4188, 2, False, curval=32514.2)
await read_registers(client, 4876, 2, False, curval=50000.0 + count)
await read_registers(client, 4878, 2, False, minval=45000.0, maxval=55000.0)


async def run_simulator():
"""Run server."""
_logger.info("### start server simulator")
cmdline = [
"--modbus_device",
"device_try",
"--modbus_server",
"server",
]
cmd_args = get_simulator_commandline(cmdline=cmdline)
task = ModbusSimulatorServer(**cmd_args)
await task.run_forever(only_start=True)

_logger.info("### start client")
client = AsyncModbusTcpClient(
"127.0.0.1",
port=5020,
framer=FramerType.SOCKET,
)
await client.connect()
assert client.connected

_logger.info("### run calls")
await run_calls(client, 1)

_logger.info("### shutdown client")
client.close()

_logger.info("### shutdown server")
await task.stop()
_logger.info("### Thanks for now.")


if __name__ == "__main__":
asyncio.run(run_simulator(), debug=True)
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__ = [
"SimActions",
"SimData",
"SimDataType",
"SimDevice",
]

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

0 comments on commit d6abf15

Please sign in to comment.