209 lines
7.5 KiB
Python
209 lines
7.5 KiB
Python
"""
|
|
Copyright (c) 2020 by RiptideIO
|
|
All rights reserved.
|
|
"""
|
|
import json
|
|
import click
|
|
import shutil
|
|
import logging
|
|
|
|
from prompt_toolkit.shortcuts import clear
|
|
from prompt_toolkit.shortcuts.progress_bar import formatters
|
|
from prompt_toolkit.styles import Style
|
|
|
|
from prompt_toolkit import PromptSession, print_formatted_text
|
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
from prompt_toolkit.completion import NestedCompleter
|
|
from prompt_toolkit.formatted_text import HTML
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
TITLE = """
|
|
__________ .______. _________
|
|
\______ \___.__. _____ ____ __| _/\_ |__ __ __ ______ / _____/ ______________ __ ___________
|
|
| ___< | |/ \ / _ \ / __ | | __ \| | \/ ___/ \_____ \_/ __ \_ __ \ \/ // __ \_ __ \\
|
|
| | \___ | Y Y ( <_> ) /_/ | | \_\ \ | /\___ \ / \ ___/| | \/\ /\ ___/| | \/
|
|
|____| / ____|__|_| /\____/\____ | |___ /____//____ > /_______ /\___ >__| \_/ \___ >__|
|
|
\/ \/ \/ \/ \/ \/ \/ \/
|
|
"""
|
|
|
|
SMALL_TITLE = "Pymodbus server..."
|
|
BOTTOM_TOOLBAR = HTML('(MODBUS SERVER) <b><style bg="ansired">Press Ctrl+C or '
|
|
'type "exit" to quit</style></b> Type "help" '
|
|
'for list of available commands')
|
|
COMMAND_ARGS = ["response_type", "error_code", "delay_by",
|
|
"clear_after", "data_len"]
|
|
RESPONSE_TYPES = ["normal", "error", "delayed", "empty", "stray"]
|
|
COMMANDS = {
|
|
"manipulator": {
|
|
"response_type": None,
|
|
"error_code": None,
|
|
"delay_by": None,
|
|
"clear_after": None
|
|
},
|
|
"exit": None,
|
|
"help": None,
|
|
"clear": None
|
|
}
|
|
USAGE = "manipulator response_type=|normal|error|delayed|empty|stray \n" \
|
|
"\tAdditional parameters\n" \
|
|
"\t\terror_code=<int> \n\t\tdelay_by=<in seconds> \n\t\t" \
|
|
"clear_after=<clear after n messages int>" \
|
|
"\n\t\tdata_len=<length of stray data (int)>\n" \
|
|
"\n\tExample usage: \n\t" \
|
|
"1. Send error response 3 for 4 requests\n\t" \
|
|
" <ansiblue>manipulator response_type=error error_code=3 clear_after=4</ansiblue>\n\t" \
|
|
"2. Delay outgoing response by 5 seconds indefinitely\n\t" \
|
|
" <ansiblue>manipulator response_type=delayed delay_by=5</ansiblue>\n\t" \
|
|
"3. Send empty response\n\t" \
|
|
" <ansiblue>manipulator response_type=empty</ansiblue>\n\t" \
|
|
"4. Send stray response of lenght 12 and revert to normal after 2 responses\n\t" \
|
|
" <ansiblue>manipulator response_type=stray data_len=11 clear_after=2</ansiblue>\n\t" \
|
|
"5. To disable response manipulation\n\t" \
|
|
" <ansiblue>manipulator response_type=normal</ansiblue>"
|
|
COMMAND_HELPS = {
|
|
"manipulator": "Manipulate response from server.\nUsage: {}".format(USAGE),
|
|
"clear": "Clears screen"
|
|
|
|
}
|
|
|
|
|
|
STYLE = Style.from_dict({"": "cyan"})
|
|
CUSTOM_FORMATTERS = [
|
|
formatters.Label(suffix=": "),
|
|
formatters.Bar(start="|", end="|", sym_a="#", sym_b="#", sym_c="-"),
|
|
formatters.Text(" "),
|
|
formatters.Text(" "),
|
|
formatters.TimeElapsed(),
|
|
formatters.Text(" "),
|
|
]
|
|
|
|
|
|
def info(message):
|
|
if not isinstance(message, str):
|
|
message = str(message)
|
|
click.secho(message, fg="green")
|
|
|
|
|
|
def warning(message):
|
|
click.secho(str(message), fg="yellow")
|
|
|
|
|
|
def error(message):
|
|
click.secho(str(message), fg="red")
|
|
|
|
|
|
def get_terminal_width():
|
|
return shutil.get_terminal_size()[0]
|
|
|
|
|
|
def print_help():
|
|
print_formatted_text(HTML("<u>Available commands:</u>"))
|
|
for cmd, hlp in sorted(COMMAND_HELPS.items()):
|
|
print_formatted_text(
|
|
HTML("<skyblue>{:45s}</skyblue><seagreen>{:100s}</seagreen>".format(cmd, hlp))
|
|
)
|
|
|
|
|
|
async def interactive_shell(server):
|
|
"""
|
|
CLI interactive shell
|
|
"""
|
|
col = get_terminal_width()
|
|
max_len = max([len(t) for t in TITLE.split("\n")])
|
|
if col > max_len:
|
|
info(TITLE)
|
|
else:
|
|
print_formatted_text(HTML('<u><b><style color="green">{}'
|
|
'</style></b></u>'.format(SMALL_TITLE)))
|
|
info("")
|
|
completer = NestedCompleter.from_nested_dict(COMMANDS)
|
|
session = PromptSession("SERVER > ",
|
|
completer=completer,
|
|
bottom_toolbar=BOTTOM_TOOLBAR)
|
|
|
|
# Run echo loop. Read text from stdin, and reply it back.
|
|
while True:
|
|
try:
|
|
invalid_command = False
|
|
result = await session.prompt_async()
|
|
if result == "exit":
|
|
await server.web_app.shutdown()
|
|
break
|
|
if result == "help":
|
|
print_help()
|
|
continue
|
|
if result == "clear":
|
|
clear()
|
|
continue
|
|
command = result.split()
|
|
if command:
|
|
if command[0] not in COMMANDS:
|
|
invalid_command = True
|
|
if invalid_command:
|
|
warning("Invalid command or invalid usage of command - {}".format(command))
|
|
continue
|
|
if len(command) == 1:
|
|
warning("Usage: '{}'".format(USAGE))
|
|
else:
|
|
args = command[1:]
|
|
skip_next = False
|
|
val_dict = {}
|
|
for index, arg in enumerate(args):
|
|
if skip_next:
|
|
skip_next = False
|
|
continue
|
|
if "=" in arg:
|
|
arg, value = arg.split("=")
|
|
else:
|
|
if arg in COMMAND_ARGS:
|
|
try:
|
|
value = args[index+1]
|
|
skip_next = True
|
|
except IndexError:
|
|
error("Missing value "
|
|
"for argument - {}".format(arg))
|
|
warning("Usage: '{}'".format(USAGE))
|
|
break
|
|
valid = True
|
|
if arg == "response_type":
|
|
if value not in RESPONSE_TYPES:
|
|
warning("Invalid response "
|
|
"type request - {}".format(value))
|
|
warning("Choose from {}".format(RESPONSE_TYPES))
|
|
valid = False
|
|
elif arg in ["error_code", "delay_by",
|
|
"clear_after", "data_len"]:
|
|
try:
|
|
value = int(value)
|
|
except ValueError:
|
|
warning("Expected integer "
|
|
"value for {}".format(arg))
|
|
valid = False
|
|
|
|
if valid:
|
|
val_dict[arg] = value
|
|
if val_dict:
|
|
server.update_manipulator_config(val_dict)
|
|
# server.manipulator_config = val_dict
|
|
# result = await run_command(tester, *command)
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
|
|
|
|
async def main(server):
|
|
# with patch_stdout():
|
|
try:
|
|
await interactive_shell(server)
|
|
finally:
|
|
pass
|
|
warning("Bye Bye!!!")
|
|
|
|
|
|
async def run_repl(server):
|
|
await main(server)
|
|
|
|
|