Skip to content

Commit

Permalink
Draft: Repl Server documentation bug fixes (#1016)
Browse files Browse the repository at this point in the history
* REPL updates

Update repl server documentation, use typer for intuitive cli experiance, update server to use different slave contexts when multiple slave id's are supported

Update typer requirement

Co-authored-by: jan Iversen <jancasacondor@gmail.com>
  • Loading branch information
dhoomakethu and janiversen authored Aug 8, 2022
1 parent 9bca66d commit 6701233
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 464 deletions.
18 changes: 17 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,31 @@ If you think, that something in the code is broken/not running well, please `ope
------------------------------------------------------------
Pymodbus REPL (Read Evaluate Print Loop)
------------------------------------------------------------

~~~~~~~~~~~~~~~~~~~~~
Pymodbus REPL Client
~~~~~~~~~~~~~~~~~~~~~

Pymodbus REPL comes with many handy features such as payload decoder
to directly retrieve the values in desired format and supports all
the diagnostic function codes directly .

For more info on REPL refer `Pymodbus REPL <https://github.com/riptideio/pymodbus/tree/master/pymodbus/repl>`_
For more info on REPL Client refer `Pymodbus REPL Client <https://github.com/riptideio/pymodbus/tree/dev/pymodbus/repl>`_

.. image:: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png
:target: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o

~~~~~~~~~~~~~~~~~~~~~
Pymodbus REPL Server
~~~~~~~~~~~~~~~~~~~~~

Pymodbus also comes with a REPL server to quickly run an asynchronous server with additional capabilities out of the box like simulating errors, delay, mangled messages etc.

For more infor on REPL Server refer `Pymodbus REPL Server <https://github.com/riptideio/pymodbus/tree/dev/pymodbus/repl>`_

.. image:: https://img.youtube.com/vi/OutaVz0JkWg/maxresdefault.jpg
:target: https://youtu.be/OutaVz0JkWg

------------------------------------------------------------
Installing
------------------------------------------------------------
Expand Down
318 changes: 6 additions & 312 deletions pymodbus/repl/README.md

Large diffs are not rendered by default.

315 changes: 315 additions & 0 deletions pymodbus/repl/client/README.md

Large diffs are not rendered by default.

61 changes: 27 additions & 34 deletions pymodbus/repl/client/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Pymodbus REPL Entry point."""
# pylint: disable=anomalous-backslash-in-string
# flake8: noqa: W605
import logging
import os.path
import sys
import pathlib

try:
import click
Expand Down Expand Up @@ -37,20 +39,21 @@
ModbusSocketFramer,
)

_logger = logging.getLogger()

click.disable_unicode_literals_warning = True

TITLE = (
r"----------------------------------------------------------------------------"
r"__________ _____ .___ __________ .__ "
r"\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | | "
r" | ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| | "
r" | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__"
r" |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/"
r" \/ \/ \/ \/ \/ \/|__|"
f" v1.3.0 - {version}"
r"----------------------------------------------------------------------------"
)
_logger = logging.getLogger(__name__)
TITLE = f"""
----------------------------------------------------------------------------
__________ _____ .___ __________ .__
\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | |
| ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| |
| | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__
|____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/
\/ \/ \/ \/ \/ \/|__|
v1.3.0 - {version}
----------------------------------------------------------------------------
"""


style = Style.from_dict(
Expand Down Expand Up @@ -104,22 +107,15 @@ def convert(self, value, param, ctx):
if ctx.token_normalize_func(choice) == value:
return choice

self.fail(
"invalid choice: %s. (choose from %s)" # pylint: disable=consider-using-f-string
% (
value,
", ".join(self.choices),
),
param,
ctx,
)
self.fail('invalid choice: %s. (choose from %s)' % # pylint: disable=consider-using-f-string
(value, ', '.join(self.choices)), param, ctx)
return None


def cli(client): # noqa: C901 pylint: disable=too-complex
"""Run client definition."""
use_keys = KeyBindings()
history_file = os.path.normpath(os.path.expanduser("~/.pymodhis"))
history_file = pathlib.Path.home().joinpath(".pymodhis")

@use_keys.add("c-space")
def _(event):
Expand Down Expand Up @@ -175,17 +171,14 @@ def _process_args(args, string=True):
break
return kwargs, execute

session = PromptSession(
lexer=PygmentsLexer(PythonLexer),
completer=CmdCompleter(client),
style=style,
complete_while_typing=True,
bottom_toolbar=bottom_toolbar,
key_bindings=use_keys,
history=FileHistory(history_file),
auto_suggest=AutoSuggestFromHistory(),
)
click.secho(f"{TITLE}", fg="green")
session = PromptSession(lexer=PygmentsLexer(PythonLexer),
completer=CmdCompleter(client), style=style,
complete_while_typing=True,
bottom_toolbar=bottom_toolbar,
key_bindings=use_keys,
history=FileHistory(history_file),
auto_suggest=AutoSuggestFromHistory())
click.secho(TITLE, fg='green')
result = None
while True: # pylint: disable=too-many-nested-blocks
try:
Expand Down
34 changes: 20 additions & 14 deletions pymodbus/repl/client/mclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,29 @@ class ExtendedRequestSupport: # pylint: disable=(too-many-public-methods

@staticmethod
def _process_exception(resp, **kwargs):
"""Process exception."""
if not kwargs.get("unit"):
err = {"message": "Broadcast message, ignoring errors!!!"}
elif isinstance(resp, ExceptionResponse):
"""Set internal process exception."""
unit = kwargs.get("unit")
if unit == 0: # pylint: disable=compare-to-zero,disable=consider-using-assignment-expr
err = {
"original_function_code": f"{resp.original_code} ({hex(resp.original_code)})",
"error_function_code": f"{resp.function_code} ({hex(resp.function_code)})",
"exception code": resp.exception_code,
"message": ModbusExceptions.decode(resp.exception_code),
}
elif isinstance(resp, ModbusIOException):
err = {
"original_function_code": f"{resp.fcode} ({hex(resp.fcode)})",
"error": resp.message,
"message": "Broadcast message, ignoring errors!!!"
}
else:
err = {"error": str(resp)}
if isinstance(resp, ExceptionResponse): # pylint: disable=else-if-used
err = {
'original_function_code': f"{resp.original_code} ({hex(resp.original_code)})",
'error_function_code': f"{resp.function_code} ({hex(resp.function_code)})",
'exception code': resp.exception_code,
'message': ModbusExceptions.decode(resp.exception_code)
}
elif isinstance(resp, ModbusIOException):
err = {
'original_function_code': f"{resp.fcode} ({hex(resp.fcode)})",
'error': resp.message
}
else:
err = {
'error': str(resp)
}
return err

def read_coils(self, address, count=1, **kwargs):
Expand Down
111 changes: 111 additions & 0 deletions pymodbus/repl/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Pymodbus REPL Server

Pymodbus REPL server helps to quicky spin an [asynchronous server](../../../examples/common/asyncio_server.py) from command line.

Support both `Modbus TCP` and `Modbus RTU` server.


Some features offered are

---
1. Runs a [reactive server](../../server/reactive/main.py) in `REPL` and `NON REPL` mode.
2. Exposes REST API's to manipulate the behaviour of the server in non repl mode.
3. Ability to manipulate the out-going response dynamically (either via REPL console or via REST API request).
4. Ability to delay the out-going response dynamically (either via REPL console or via REST API request).
5. Auto revert to normal response after pre-defined number of manipulated responses.

## Installation
Install `pymodbus` with the required dependencies

`pip install pymodbus[repl]`

## Usage

Invoke REPL server with `pymodbus.server run` command.

```shell
✗ pymodbus.server --help

Usage: pymodbus.server [OPTIONS] COMMAND [ARGS]...

Reactive modebus server

╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --host TEXT Host address [default: localhost] │
│ --web-port INTEGER Web app port [default: 8080] │
│ -b Support broadcast messages │
│ --repl --no-repl Enable/Disable repl for server [default: repl] │
│ --verbose --no-verbose Run with debug logs enabled for pymodbus [default: no-verbose] │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the │
│ installation. │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ run Run Reactive Modbus server. │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```

```shell
✗ pymodbus.server run --help

Usage: pymodbus.server run [OPTIONS]

Run Reactive Modbus server.
Exposing REST endpoint for response manipulation.

╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --modbus-server -s TEXT Modbus Server [default: ModbusServerTypes.tcp] │
│ --framer -f TEXT Modbus framer to use [default: ModbusFramerTypes.socket] │
│ --modbus-port -p TEXT Modbus port [default: 5020] │
│ --unit-id -u INTEGER Supported Modbus unit id's [default: None] │
│ --modbus-config PATH Path to additional modbus server config [default: None] │
│ --random -r INTEGER Randomize every `r` reads. 0=never, 1=always,2=every-second-read, and so on. Applicable │
│ IR and DI. │
│ [default: 0] │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
### Pymodbus Server REPL mode
The REPL server comes with auto-completion and can be installed for the supported shell with `pymodbus.server --install-completion <shell>`.
Don't forget to restart the terminal for the auto-completion to kick-in. Use `TAB` key to show auto-completion.

Example usage.

```shell
✗ pymodbus.server run --modbus-server tcp --framer socket --unit-id 1 --unit-id 4 --random 2
__________ .______. _________
\______ \___.__. _____ ____ __| _/\_ |__ __ __ ______ / _____/ ______________ __ ___________
| ___< | |/ \ / _ \ / __ | | __ \| | \/ ___/ \_____ \_/ __ \_ __ \ \/ // __ \_ __ \\
| | \___ | Y Y ( <_> ) /_/ | | \_\ \ | /\___ \ / \ ___/| | \/\ /\ ___/| | \/
|____| / ____|__|_| /\____/\____ | |___ /____//____ > /_______ /\___ >__| \_/ \___ >__|
\/ \/ \/ \/ \/ \/ \/ \/
SERVER > help
Available commands:
clear Clears screen
manipulator Manipulate response from server.
Usage: manipulator response_type=|normal|error|delayed|empty|stray
Additional parameters
error_code=<int>
delay_by=<in seconds>
clear_after=<clear after n messages int>
data_len=<length of stray data (int)>
Example usage:
1. Send error response 3 for 4 requests
manipulator response_type=error error_code=3 clear_after=4
2. Delay outgoing response by 5 seconds indefinitely
manipulator response_type=delayed delay_by=5
3. Send empty response
manipulator response_type=empty
4. Send stray response of length 12 and revert to normal after 2 responses
manipulator response_type=stray data_len=11 clear_after=2
5. To disable response manipulation
manipulator response_type=normal
```

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

0 comments on commit 6701233

Please sign in to comment.