Skip to content

Commit 30f7166

Browse files
Repl enhancements 2 (#1141)
* Fix #1118 for python 3.9 and above * Solve isort. * Support randomizing reads for discrete inputs and input registers * Fix pylint error for zero comparision * Fix server help commands * Fix Reactive server not starting in non-repl mode * Update REPL server documentation * Codespell fix Co-authored-by: jan Iversen <[email protected]>
1 parent 660c697 commit 30f7166

File tree

6 files changed

+327
-58
lines changed

6 files changed

+327
-58
lines changed

pymodbus/repl/server/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,104 @@ Usage: manipulator response_type=|normal|error|delayed|empty|stray
120120
```
121121

122122
[![Pymodbus Server REPL](https://img.youtube.com/vi/OutaVz0JkWg/maxresdefault.jpg)](https://youtu.be/OutaVz0JkWg)
123+
124+
125+
### Pymodbus Server Non REPL Mode
126+
To run the Reactive server in the non-repl mode use `--no-repl` flag while starting the server. The server responses can still be manipulated with REST API calls.
127+
128+
```
129+
pymodbus.server --no-repl --verbose run --modbus-server tcp --framer socket --unit-id 1 --unit-id 4 --random 2 --modbus-port 5020
130+
2022-10-27 13:32:56,062 MainThread INFO main :246 Modbus server started
131+
132+
Reactive Modbus Server started.
133+
======== Running on http://localhost:8080 ========
134+
135+
===========================================================================
136+
Example Usage:
137+
curl -X POST http://localhost:8080 -d "{"response_type": "error", "error_code": 4}"
138+
===========================================================================
139+
```
140+
141+
#### REST API
142+
143+
The server response can be manipulated by doing a `POST` request on the web-server running `http://<host>:<port>`. The values for `host` and `port`
144+
can be modified with `--host` and `--web-port` params while starting the server. The default values are `localhost` and `8080`
145+
146+
```
147+
pymodbus.server --host <host-ip> --web-port <new-port> run .....
148+
149+
```
150+
151+
The payload for the `POST` requests is
152+
153+
```
154+
{
155+
"response_type": "normal", # normal, error, delayed, empty, stray
156+
"delay_by": <int>,
157+
"data_len": <int>,
158+
"error_code": <int>,
159+
"clear_after": <int>, # request count
160+
}
161+
```
162+
163+
* `response_type` : Response expected from the server.
164+
* `normal` : Normal response, no errors
165+
* `error`: Return error responses , requires additional `error_code` field for the modbus error code to be returned.
166+
* `error_code`: Error code to return, possible values are
167+
* ```
168+
0x01 # IllegalFunction
169+
0x02 # IllegalAddress
170+
0x03 # IllegalValue
171+
0x04 # SlaveFailure
172+
0x05 # Acknowledge
173+
0x06 # SlaveBusy
174+
0x08 # MemoryParityError
175+
0x0A # GatewayPathUnavailable
176+
0x0B # GatewayNoResponse
177+
178+
```
179+
* `delayed`: Responses are delayed by the time specified with `delay_by` field.
180+
* `delay_by`: Delay the response by <n> seconds (`float`).
181+
* `empty`: Returns an empty response or no response
182+
* `stray`: Returns stray/junk response with length specified with `data_len` field.
183+
* `data_len`: Length of the stray_data (`int`)
184+
* `clear_after`: Clears the error responses after <n> requests (`int`)
185+
186+
**EXAMPLES**
187+
188+
**Return exception response 0x02 Illegal Address and clear after 5 requests**
189+
190+
```
191+
curl -X POST http://localhost:8080 -d '{"response_type": "error", "error_code": 2, "clear_after": 5}'
192+
```
193+
194+
**Return Empty response**
195+
196+
```
197+
curl -X POST http://localhost:8080 -d '{"response_type": "empty"}'
198+
```
199+
200+
**Return Stray response of length 25bytes**
201+
202+
```
203+
curl -X POST http://localhost:8080 -d '{"response_type": "stray", "data_len": 25}'
204+
205+
```
206+
207+
**Delay responses by 3 seconds**
208+
209+
```
210+
curl -X POST http://localhost:8080 -d '{"response_type": "delayed", "delay_by": 3}'
211+
212+
```
213+
214+
**Revert to normal responses**
215+
216+
```
217+
curl -X POST http://localhost:8080 -d '{"response_type": "normal"}'
218+
219+
```
220+
221+
## TODO
222+
223+
* Add REST Api endpoint to view current manipulator config.

pymodbus/repl/server/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def print_help():
104104
print_formatted_text(HTML("<u>Available commands:</u>"))
105105
for cmd, hlp in sorted(COMMAND_HELPS.items()):
106106
print_formatted_text(
107-
HTML("<skyblue>{cmd:45s}</skyblue><seagreen>{hlp:100s}</seagreen>")
107+
HTML(f"<skyblue>{cmd:45s}</skyblue><seagreen>{hlp:100s}</seagreen>")
108108
)
109109

110110

pymodbus/repl/server/main.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def run(
163163
else:
164164
modbus_config = DEFAULT_CONFIG
165165

166+
data_block_settings = modbus_config.pop("data_block_settings", {})
166167
modbus_config = modbus_config.get(modbus_server, {})
167-
168168
if modbus_server != "serial":
169169
modbus_port = int(modbus_port)
170170
handler = modbus_config.pop("handler", "ModbusConnectedRequestHandler")
@@ -181,17 +181,15 @@ def run(
181181
unit=modbus_unit_id,
182182
loop=loop,
183183
single=False,
184+
data_block_settings=data_block_settings,
184185
**web_app_config,
185186
**modbus_config,
186187
)
187188
try:
189+
loop.run_until_complete(app.run_async(repl))
188190
if repl:
189-
loop.run_until_complete(app.run_async())
190-
191191
loop.run_until_complete(run_repl(app))
192-
loop.run_forever()
193-
else:
194-
app.run()
192+
loop.run_forever()
195193

196194
except CANCELLED_ERROR:
197195
print("Done!!!!!")
Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,67 @@
11
{
22
"tcp": {
3-
"handler": "ModbusConnectedRequestHandler",
4-
"allow_reuse_address": true,
5-
"allow_reuse_port": true,
6-
"backlog": 20,
7-
"ignore_missing_slaves": false
8-
},
9-
"rtu": {
10-
"handler": "ModbusSingleRequestHandler",
11-
"stopbits": 1,
12-
"bytesize": 8,
13-
"parity": "N",
14-
"baudrate": 9600,
15-
"timeout": 3,
16-
"auto_reconnect": false,
17-
"reconnect_delay": 2
18-
},
19-
"tls": {
20-
"handler": "ModbusConnectedRequestHandler",
21-
"certfile": null,
22-
"keyfile": null,
23-
"allow_reuse_address": true,
24-
"allow_reuse_port": true,
25-
"backlog": 20,
26-
"ignore_missing_slaves": false
27-
},
28-
"udp": {
29-
"handler": "ModbusDisonnectedRequestHandler",
30-
"ignore_missing_slaves": false
31-
}
3+
"handler": "ModbusConnectedRequestHandler",
4+
"allow_reuse_address": true,
5+
"allow_reuse_port": true,
6+
"backlog": 20,
7+
"ignore_missing_slaves": false
8+
},
9+
"serial": {
10+
"handler": "ModbusSingleRequestHandler",
11+
"stopbits": 1,
12+
"bytesize": 8,
13+
"parity": "N",
14+
"baudrate": 9600,
15+
"timeout": 3,
16+
"auto_reconnect": false,
17+
"reconnect_delay": 2
18+
},
19+
"tls": {
20+
"handler": "ModbusConnectedRequestHandler",
21+
"certfile": null,
22+
"keyfile": null,
23+
"allow_reuse_address": true,
24+
"allow_reuse_port": true,
25+
"backlog": 20,
26+
"ignore_missing_slaves": false
27+
},
28+
"udp": {
29+
"handler": "ModbusDisonnectedRequestHandler",
30+
"ignore_missing_slaves": false
31+
},
32+
"data_block_settings": {
33+
"min_binary_value": 0,
34+
"max_binary_value": 1,
35+
"min_register_value": 0,
36+
"max_register_value": 100,
37+
"data_block": {
38+
"discrete_inputs": {
39+
"block_start": 0,
40+
"block_size": 100,
41+
"default": 0,
42+
"sparse": false
43+
},
44+
"coils": {
45+
"block_start": 0,
46+
"block_size": 100,
47+
"default": 0,
48+
"sparse": false
49+
},
50+
"holding_registers": {
51+
"block_start": 0,
52+
"block_size": 100,
53+
"default": 0,
54+
"sparse": false
55+
},
56+
"input_registers": {
57+
"block_start": 0,
58+
"block_size": 100,
59+
"default": 0,
60+
"sparse": false
61+
}
62+
63+
}
64+
65+
66+
}
3267
}

pymodbus/server/reactive/default_config.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Configuration for Pymodbus REPL Reactive Module."""
22

3-
DEFAULT_CONFIG = { # pylint: disable=consider-using-namedtuple-or-dataclass
3+
DEFAULT_CONFIG = {
44
"tcp": {
55
"handler": "ModbusConnectedRequestHandler",
66
"allow_reuse_address": True,
@@ -31,4 +31,38 @@
3131
"handler": "ModbusDisonnectedRequestHandler",
3232
"ignore_missing_slaves": False,
3333
},
34+
"data_block_settings": {
35+
"min_binary_value": 0, # For coils and DI
36+
"max_binary_value": 1, # For coils and DI
37+
"min_register_value": 0, # For Holding and input registers
38+
"max_register_value": 65535, # For Holding and input registers
39+
"data_block": {
40+
"discrete_inputs": {
41+
"block_start": 0, # Block start
42+
"block_size": 100, # Block end
43+
"default": 0, # Default value,
44+
"sparse": False,
45+
},
46+
"coils": {
47+
"block_start": 0,
48+
"block_size": 100,
49+
"default": 0,
50+
"sparse": False,
51+
},
52+
"holding_registers": {
53+
"block_start": 0,
54+
"block_size": 100,
55+
"default": 0,
56+
"sparse": False,
57+
},
58+
"input_registers": {
59+
"block_start": 0,
60+
"block_size": 100,
61+
"default": 0,
62+
"sparse": False,
63+
},
64+
},
65+
},
3466
}
67+
68+
__all__ = ["DEFAULT_CONFIG"]

0 commit comments

Comments
 (0)