Skip to content
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
101 changes: 101 additions & 0 deletions pymodbus/repl/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,104 @@ Usage: manipulator response_type=|normal|error|delayed|empty|stray
```

[![Pymodbus Server REPL](https://img.youtube.com/vi/OutaVz0JkWg/maxresdefault.jpg)](https://youtu.be/OutaVz0JkWg)


### Pymodbus Server Non REPL Mode
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.

```
pymodbus.server --no-repl --verbose run --modbus-server tcp --framer socket --unit-id 1 --unit-id 4 --random 2 --modbus-port 5020
2022-10-27 13:32:56,062 MainThread INFO main :246 Modbus server started

Reactive Modbus Server started.
======== Running on http://localhost:8080 ========

===========================================================================
Example Usage:
curl -X POST http://localhost:8080 -d "{"response_type": "error", "error_code": 4}"
===========================================================================
```

#### REST API

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`
can be modified with `--host` and `--web-port` params while starting the server. The default values are `localhost` and `8080`

```
pymodbus.server --host <host-ip> --web-port <new-port> run .....

```

The payload for the `POST` requests is

```
{
"response_type": "normal", # normal, error, delayed, empty, stray
"delay_by": <int>,
"data_len": <int>,
"error_code": <int>,
"clear_after": <int>, # request count
}
```

* `response_type` : Response expected from the server.
* `normal` : Normal response, no errors
* `error`: Return error responses , requires additional `error_code` field for the modbus error code to be returned.
* `error_code`: Error code to return, possible values are
* ```
0x01 # IllegalFunction
0x02 # IllegalAddress
0x03 # IllegalValue
0x04 # SlaveFailure
0x05 # Acknowledge
0x06 # SlaveBusy
0x08 # MemoryParityError
0x0A # GatewayPathUnavailable
0x0B # GatewayNoResponse

```
* `delayed`: Responses are delayed by the time specified with `delay_by` field.
* `delay_by`: Delay the response by <n> seconds (`float`).
* `empty`: Returns an empty response or no response
* `stray`: Returns stray/junk response with length specified with `data_len` field.
* `data_len`: Length of the stray_data (`int`)
* `clear_after`: Clears the error responses after <n> requests (`int`)

**EXAMPLES**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot for adding this example section. Super useful


**Return exception response 0x02 Illegal Address and clear after 5 requests**

```
curl -X POST http://localhost:8080 -d '{"response_type": "error", "error_code": 2, "clear_after": 5}'
```

**Return Empty response**

```
curl -X POST http://localhost:8080 -d '{"response_type": "empty"}'
```

**Return Stray response of length 25bytes**

```
curl -X POST http://localhost:8080 -d '{"response_type": "stray", "data_len": 25}'

```

**Delay responses by 3 seconds**

```
curl -X POST http://localhost:8080 -d '{"response_type": "delayed", "delay_by": 3}'

```

**Revert to normal responses**

```
curl -X POST http://localhost:8080 -d '{"response_type": "normal"}'

```

## TODO

* Add REST Api endpoint to view current manipulator config.
2 changes: 1 addition & 1 deletion pymodbus/repl/server/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def print_help():
print_formatted_text(HTML("<u>Available commands:</u>"))
for cmd, hlp in sorted(COMMAND_HELPS.items()):
print_formatted_text(
HTML("<skyblue>{cmd:45s}</skyblue><seagreen>{hlp:100s}</seagreen>")
HTML(f"<skyblue>{cmd:45s}</skyblue><seagreen>{hlp:100s}</seagreen>")
)


Expand Down
10 changes: 4 additions & 6 deletions pymodbus/repl/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ def run(
else:
modbus_config = DEFAULT_CONFIG

data_block_settings = modbus_config.pop("data_block_settings", {})
modbus_config = modbus_config.get(modbus_server, {})

if modbus_server != "serial":
modbus_port = int(modbus_port)
handler = modbus_config.pop("handler", "ModbusConnectedRequestHandler")
Expand All @@ -181,17 +181,15 @@ def run(
unit=modbus_unit_id,
loop=loop,
single=False,
data_block_settings=data_block_settings,
**web_app_config,
**modbus_config,
)
try:
loop.run_until_complete(app.run_async(repl))
if repl:
loop.run_until_complete(app.run_async())

loop.run_until_complete(run_repl(app))
loop.run_forever()
else:
app.run()
loop.run_forever()

except CANCELLED_ERROR:
print("Done!!!!!")
Expand Down
93 changes: 64 additions & 29 deletions pymodbus/server/reactive/default_config.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,67 @@
{
"tcp": {
"handler": "ModbusConnectedRequestHandler",
"allow_reuse_address": true,
"allow_reuse_port": true,
"backlog": 20,
"ignore_missing_slaves": false
},
"rtu": {
"handler": "ModbusSingleRequestHandler",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 9600,
"timeout": 3,
"auto_reconnect": false,
"reconnect_delay": 2
},
"tls": {
"handler": "ModbusConnectedRequestHandler",
"certfile": null,
"keyfile": null,
"allow_reuse_address": true,
"allow_reuse_port": true,
"backlog": 20,
"ignore_missing_slaves": false
},
"udp": {
"handler": "ModbusDisonnectedRequestHandler",
"ignore_missing_slaves": false
}
"handler": "ModbusConnectedRequestHandler",
"allow_reuse_address": true,
"allow_reuse_port": true,
"backlog": 20,
"ignore_missing_slaves": false
},
"serial": {
"handler": "ModbusSingleRequestHandler",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 9600,
"timeout": 3,
"auto_reconnect": false,
"reconnect_delay": 2
},
"tls": {
"handler": "ModbusConnectedRequestHandler",
"certfile": null,
"keyfile": null,
"allow_reuse_address": true,
"allow_reuse_port": true,
"backlog": 20,
"ignore_missing_slaves": false
},
"udp": {
"handler": "ModbusDisonnectedRequestHandler",
"ignore_missing_slaves": false
},
"data_block_settings": {
"min_binary_value": 0,
"max_binary_value": 1,
"min_register_value": 0,
"max_register_value": 100,
"data_block": {
"discrete_inputs": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": false
},
"coils": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": false
},
"holding_registers": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": false
},
"input_registers": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": false
}

}


}
}
36 changes: 35 additions & 1 deletion pymodbus/server/reactive/default_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Configuration for Pymodbus REPL Reactive Module."""

DEFAULT_CONFIG = { # pylint: disable=consider-using-namedtuple-or-dataclass
DEFAULT_CONFIG = {
"tcp": {
"handler": "ModbusConnectedRequestHandler",
"allow_reuse_address": True,
Expand Down Expand Up @@ -31,4 +31,38 @@
"handler": "ModbusDisonnectedRequestHandler",
"ignore_missing_slaves": False,
},
"data_block_settings": {
"min_binary_value": 0, # For coils and DI
"max_binary_value": 1, # For coils and DI
"min_register_value": 0, # For Holding and input registers
"max_register_value": 65535, # For Holding and input registers
"data_block": {
"discrete_inputs": {
"block_start": 0, # Block start
"block_size": 100, # Block end
"default": 0, # Default value,
"sparse": False,
},
"coils": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": False,
},
"holding_registers": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": False,
},
"input_registers": {
"block_start": 0,
"block_size": 100,
"default": 0,
"sparse": False,
},
},
},
}

__all__ = ["DEFAULT_CONFIG"]
Loading