modsim 0.5.2__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modsim
3
- Version: 0.5.2
3
+ Version: 1.0.0
4
4
  Summary: A Simple Modbus TCP Device Simulator used for modpoll tool
5
5
  Home-page: https://github.com/gavinying/modsim
6
6
  Author: Ying Shaodong
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
14
  Requires-Dist: pymodbus (>=3.6.9,<3.7.0)
16
15
  Project-URL: Documentation, https://gavinying.github.io/modsim/
17
16
  Project-URL: Repository, https://github.com/gavinying/modsim
@@ -89,8 +88,12 @@ To verify it, you can test by [modpoll](https://github.com/gavinying/modpoll) to
89
88
  modpoll \
90
89
  --tcp localhost \
91
90
  --tcp-port 5020 \
92
- --config https://raw.githubusercontent.com/gavinying/modpoll/master/examples/modsim.csv
91
+ --config https://raw.githubusercontent.com/gavinying/modpoll/main/examples/modsim.csv
93
92
  ```
94
93
 
95
94
  > **_Note_**: add `sudo` before `docker run` command if you want to use the Modbus TCP standard port `502`.
96
95
 
96
+ ## Local server behavior
97
+
98
+ Run the simulator without Docker with `python -m modsim --comm tcp --port 5020`. The default server exposes the four Modbus tables with the sample data shown above. Coil and holding-register writes are supported; discrete inputs and input registers are read-only. Requests outside an initialized address range return a Modbus exception. The server handles SIGINT and SIGTERM by stopping its listener so the port can be reused after shutdown.
99
+
@@ -70,7 +70,11 @@ To verify it, you can test by [modpoll](https://github.com/gavinying/modpoll) to
70
70
  modpoll \
71
71
  --tcp localhost \
72
72
  --tcp-port 5020 \
73
- --config https://raw.githubusercontent.com/gavinying/modpoll/master/examples/modsim.csv
73
+ --config https://raw.githubusercontent.com/gavinying/modpoll/main/examples/modsim.csv
74
74
  ```
75
75
 
76
76
  > **_Note_**: add `sudo` before `docker run` command if you want to use the Modbus TCP standard port `502`.
77
+
78
+ ## Local server behavior
79
+
80
+ Run the simulator without Docker with `python -m modsim --comm tcp --port 5020`. The default server exposes the four Modbus tables with the sample data shown above. Coil and holding-register writes are supported; discrete inputs and input registers are read-only. Requests outside an initialized address range return a Modbus exception. The server handles SIGINT and SIGTERM by stopping its listener so the port can be reused after shutdown.
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -25,6 +25,8 @@ The corresponding client can be started as:
25
25
 
26
26
  import os
27
27
  import asyncio
28
+ import signal
29
+ from contextlib import suppress
28
30
  from typing import Any
29
31
 
30
32
  from pymodbus.constants import Endian
@@ -40,6 +42,7 @@ from pymodbus.server import (
40
42
  StartAsyncTcpServer,
41
43
  StartAsyncTlsServer,
42
44
  StartAsyncUdpServer,
45
+ ServerAsyncStop,
43
46
  )
44
47
 
45
48
  from . import __version__
@@ -225,10 +228,51 @@ def app() -> None:
225
228
  )
226
229
 
227
230
  run_args = setup_server(get_commandline())
228
- asyncio.run(run_async_server(run_args), debug=True)
231
+ asyncio.run(_run_server_with_signals(run_args), debug=True)
229
232
 
230
233
  _logger.info("exiting.")
231
234
 
232
235
 
236
+ async def _run_server_with_signals(args: Any) -> None:
237
+ """Run the server and close its listener when the process is signaled."""
238
+ loop = asyncio.get_running_loop()
239
+ server_task = asyncio.create_task(run_async_server(args))
240
+ stop_requested = False
241
+
242
+ async def stop_server() -> None:
243
+ for _ in range(100):
244
+ if server_task.done():
245
+ return
246
+ try:
247
+ await ServerAsyncStop()
248
+ return
249
+ except (AttributeError, RuntimeError):
250
+ await asyncio.sleep(0.01)
251
+
252
+ def request_stop() -> None:
253
+ nonlocal stop_requested
254
+ if not stop_requested:
255
+ stop_requested = True
256
+ loop.create_task(stop_server())
257
+
258
+ installed_signals = []
259
+ for signum in (signal.SIGINT, signal.SIGTERM):
260
+ try:
261
+ loop.add_signal_handler(signum, request_stop)
262
+ installed_signals.append(signum)
263
+ except (NotImplementedError, RuntimeError):
264
+ pass
265
+
266
+ try:
267
+ await server_task
268
+ finally:
269
+ for signum in installed_signals:
270
+ loop.remove_signal_handler(signum)
271
+ if not server_task.done():
272
+ server_task.cancel()
273
+ with suppress(asyncio.CancelledError):
274
+ await server_task
275
+
276
+
233
277
  if __name__ == "__main__":
234
278
  app()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "modsim"
3
- version = "v0.5.2"
3
+ version = "v1.0.0"
4
4
  description = "A Simple Modbus TCP Device Simulator used for modpoll tool"
5
5
  authors = ["Ying Shaodong <helloysd@gmail.com>"]
6
6
  repository = "https://github.com/gavinying/modsim"
@@ -40,6 +40,7 @@ show_error_codes = true
40
40
 
41
41
  [tool.pytest.ini_options]
42
42
  testpaths = ["tests"]
43
+ markers = ["integration: local protocol integration test"]
43
44
 
44
45
  [tool.poetry.scripts]
45
46
  modsim = "modsim.main:app"
@@ -1 +0,0 @@
1
- __version__ = "0.5.2"
File without changes
File without changes
File without changes