initial
This commit is contained in:
1
env/lib/python3.11/site-packages/pymodbus/repl/client/__init__.py
vendored
Normal file
1
env/lib/python3.11/site-packages/pymodbus/repl/client/__init__.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"""Repl client."""
|
||||
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/completer.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/completer.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/helper.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/helper.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/main.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/main.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/mclient.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/repl/client/__pycache__/mclient.cpython-311.pyc
vendored
Normal file
Binary file not shown.
143
env/lib/python3.11/site-packages/pymodbus/repl/client/completer.py
vendored
Normal file
143
env/lib/python3.11/site-packages/pymodbus/repl/client/completer.py
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Command Completion for pymodbus REPL."""
|
||||
from prompt_toolkit.application.current import get_app
|
||||
|
||||
# pylint: disable=missing-type-doc
|
||||
from prompt_toolkit.completion import Completer, Completion
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.styles import Style
|
||||
|
||||
from pymodbus.repl.client.helper import get_commands
|
||||
|
||||
|
||||
@Condition
|
||||
def has_selected_completion():
|
||||
"""Check for selected completion."""
|
||||
complete_state = get_app().current_buffer.complete_state
|
||||
return complete_state is not None and complete_state.current_completion is not None
|
||||
|
||||
|
||||
style = Style.from_dict(
|
||||
{
|
||||
"completion-menu.completion": "bg:#008888 #ffffff",
|
||||
"completion-menu.completion.current": "bg:#00aaaa #000000",
|
||||
"scrollbar.background": "bg:#88aaaa",
|
||||
"scrollbar.button": "bg:#222222",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CmdCompleter(Completer):
|
||||
"""Completer for Pymodbus REPL."""
|
||||
|
||||
def __init__(self, client=None, commands=None, ignore_case=True):
|
||||
"""Initialize.
|
||||
|
||||
:param client: Modbus Client
|
||||
:param commands: Commands to be added for Completion (list)
|
||||
:param ignore_case: Ignore Case while looking up for commands
|
||||
"""
|
||||
self._commands = commands or get_commands(client)
|
||||
self._commands["help"] = ""
|
||||
self._command_names = self._commands.keys()
|
||||
self.ignore_case = ignore_case
|
||||
|
||||
@property
|
||||
def commands(self):
|
||||
"""Return commands."""
|
||||
return self._commands
|
||||
|
||||
@property
|
||||
def command_names(self):
|
||||
"""Return command names."""
|
||||
return self._commands.keys()
|
||||
|
||||
def completing_command(self, words, word_before_cursor):
|
||||
"""Determine if we are dealing with supported command.
|
||||
|
||||
:param words: Input text broken in to word tokens.
|
||||
:param word_before_cursor: The current word before the cursor, \
|
||||
which might be one or more blank spaces.
|
||||
:return:
|
||||
"""
|
||||
return len(words) == 1 and len(word_before_cursor)
|
||||
|
||||
def completing_arg(self, words, word_before_cursor):
|
||||
"""Determine if we are currently completing an argument.
|
||||
|
||||
:param words: The input text broken into word tokens.
|
||||
:param word_before_cursor: The current word before the cursor, \
|
||||
which might be one or more blank spaces.
|
||||
:return: Specifies whether we are currently completing an arg.
|
||||
"""
|
||||
return len(words) > 1 and len(word_before_cursor)
|
||||
|
||||
def arg_completions(self, words, _word_before_cursor):
|
||||
"""Generate arguments completions based on the input."""
|
||||
cmd = words[0].strip()
|
||||
cmd = self._commands.get(cmd, None)
|
||||
return cmd if cmd else None
|
||||
|
||||
def _get_completions(self, word, word_before_cursor):
|
||||
"""Get completions."""
|
||||
if self.ignore_case:
|
||||
word_before_cursor = word_before_cursor.lower()
|
||||
return self.word_matches(word, word_before_cursor)
|
||||
|
||||
def word_matches(self, word, word_before_cursor):
|
||||
"""Match the word and word before cursor.
|
||||
|
||||
:param word: The input text broken into word tokens.
|
||||
:param word_before_cursor: The current word before the cursor, \
|
||||
which might be one or more blank spaces.
|
||||
:return: True if matched.
|
||||
|
||||
"""
|
||||
if self.ignore_case:
|
||||
word = word.lower()
|
||||
return word.startswith(word_before_cursor)
|
||||
|
||||
def get_completions(self, document, complete_event):
|
||||
"""Get completions for the current scope.
|
||||
|
||||
:param document: An instance of `prompt_toolkit.Document`.
|
||||
:param complete_event: (Unused).
|
||||
:return: Yields an instance of `prompt_toolkit.completion.Completion`.
|
||||
"""
|
||||
word_before_cursor = document.get_word_before_cursor(WORD=True)
|
||||
text = document.text_before_cursor.lstrip()
|
||||
words = document.text.strip().split()
|
||||
meta = None
|
||||
commands = []
|
||||
if not words:
|
||||
# yield commands
|
||||
pass
|
||||
if self.completing_command(words, word_before_cursor):
|
||||
commands = self._command_names
|
||||
c_meta = {
|
||||
k: v.help_text if not isinstance(v, str) else v
|
||||
for k, v in self._commands.items()
|
||||
}
|
||||
meta = lambda x: ( # pylint: disable=unnecessary-lambda-assignment
|
||||
x,
|
||||
c_meta.get(x, ""),
|
||||
)
|
||||
else:
|
||||
if not list(
|
||||
filter(lambda cmd: any(x == cmd for x in words), self._command_names)
|
||||
):
|
||||
# yield commands
|
||||
pass
|
||||
|
||||
if " " in text:
|
||||
command = self.arg_completions(words, word_before_cursor)
|
||||
commands = list(command.get_completion())
|
||||
commands = list(
|
||||
filter(lambda cmd: not (any(cmd in x for x in words)), commands)
|
||||
)
|
||||
meta = command.get_meta
|
||||
for command in commands:
|
||||
if self._get_completions(command, word_before_cursor):
|
||||
_, display_meta = meta(command) if meta else ("", "")
|
||||
yield Completion(
|
||||
command, -len(word_before_cursor), display_meta=display_meta
|
||||
)
|
||||
312
env/lib/python3.11/site-packages/pymodbus/repl/client/helper.py
vendored
Normal file
312
env/lib/python3.11/site-packages/pymodbus/repl/client/helper.py
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
"""Helper Module for REPL actions."""
|
||||
import inspect
|
||||
|
||||
# pylint: disable=missing-type-doc
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
import pygments
|
||||
from prompt_toolkit import print_formatted_text
|
||||
from prompt_toolkit.formatted_text import HTML, PygmentsTokens
|
||||
from pygments.lexers.data import JsonLexer
|
||||
|
||||
from pymodbus.payload import BinaryPayloadDecoder, Endian
|
||||
|
||||
|
||||
predicate = inspect.isfunction
|
||||
argspec = inspect.signature
|
||||
|
||||
|
||||
FORMATTERS = {
|
||||
"int8": "decode_8bit_int",
|
||||
"int16": "decode_16bit_int",
|
||||
"int32": "decode_32bit_int",
|
||||
"int64": "decode_64bit_int",
|
||||
"uint8": "decode_8bit_uint",
|
||||
"uint16": "decode_16bit_uint",
|
||||
"uint32": "decode_32bit_uint",
|
||||
"uint64": "decode_64bit_int",
|
||||
"float16": "decode_16bit_float",
|
||||
"float32": "decode_32bit_float",
|
||||
"float64": "decode_64bit_float",
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_KWARGS = {"slave": "Slave address"}
|
||||
|
||||
OTHER_COMMANDS = {
|
||||
"result.raw": "Show RAW Result",
|
||||
"result.decode": "Decode register response to known formats",
|
||||
}
|
||||
EXCLUDE = ["execute", "recv", "send", "trace", "set_debug"]
|
||||
CLIENT_METHODS = [
|
||||
"connect",
|
||||
"close",
|
||||
"idle_time",
|
||||
"is_socket_open",
|
||||
"get_port",
|
||||
"set_port",
|
||||
"get_stopbits",
|
||||
"set_stopbits",
|
||||
"get_bytesize",
|
||||
"set_bytesize",
|
||||
"get_parity",
|
||||
"set_parity",
|
||||
"get_baudrate",
|
||||
"set_baudrate",
|
||||
"get_timeout",
|
||||
"set_timeout",
|
||||
"get_serial_settings",
|
||||
]
|
||||
CLIENT_ATTRIBUTES: List[str] = []
|
||||
|
||||
|
||||
class Command:
|
||||
"""Class representing Commands to be consumed by Completer."""
|
||||
|
||||
def __init__(self, name, signature, doc, slave=False):
|
||||
"""Initialize.
|
||||
|
||||
:param name: Name of the command
|
||||
:param signature: inspect object
|
||||
:param doc: Doc string for the command
|
||||
:param slave: Use slave as additional argument in the command .
|
||||
"""
|
||||
self.name = name
|
||||
self.doc = doc.split("\n") if doc else " ".join(name.split("_"))
|
||||
self.help_text = self._create_help()
|
||||
self.param_help = self._create_arg_help()
|
||||
if signature:
|
||||
self._params = signature.parameters
|
||||
self.args = self.create_completion()
|
||||
else:
|
||||
self._params = ""
|
||||
|
||||
if self.name.startswith("client.") and slave:
|
||||
self.args.update(**DEFAULT_KWARGS)
|
||||
|
||||
def _create_help(self):
|
||||
"""Create help."""
|
||||
doc = filter(lambda d: d, self.doc)
|
||||
cmd_help = list(
|
||||
filter(
|
||||
lambda x: not x.startswith(":param") and not x.startswith(":return"),
|
||||
doc,
|
||||
)
|
||||
)
|
||||
return " ".join(cmd_help).strip()
|
||||
|
||||
def _create_arg_help(self):
|
||||
"""Create arg help."""
|
||||
param_dict = {}
|
||||
params = list(filter(lambda d: d.strip().startswith(":param"), self.doc))
|
||||
for param in params:
|
||||
param, param_help = param.split(":param")[1].strip().split(":")
|
||||
param_dict[param] = param_help
|
||||
return param_dict
|
||||
|
||||
def create_completion(self):
|
||||
"""Create command completion meta data.
|
||||
|
||||
:return:
|
||||
"""
|
||||
words = {}
|
||||
|
||||
def _create(entry, default):
|
||||
if entry not in ["self", "kwargs"]:
|
||||
if isinstance(default, (int, str)):
|
||||
entry += f"={default}"
|
||||
return entry
|
||||
return None
|
||||
|
||||
for arg in self._params.values():
|
||||
if entry := _create(arg.name, arg.default):
|
||||
entry, meta = self.get_meta(entry)
|
||||
words[entry] = meta
|
||||
|
||||
return words
|
||||
|
||||
def get_completion(self):
|
||||
"""Get a list of completions.
|
||||
|
||||
:return:
|
||||
"""
|
||||
return self.args.keys()
|
||||
|
||||
def get_meta(self, cmd):
|
||||
"""Get Meta info of a given command.
|
||||
|
||||
:param cmd: Name of command.
|
||||
:return: Dict containing meta info.
|
||||
"""
|
||||
cmd = cmd.strip()
|
||||
cmd = cmd.split("=")[0].strip()
|
||||
return cmd, self.param_help.get(cmd, "")
|
||||
|
||||
def __str__(self):
|
||||
"""Return string representation."""
|
||||
if self.doc:
|
||||
return f"Command {self.name:>50}{self.doc:<20}"
|
||||
return f"Command {self.name}"
|
||||
|
||||
|
||||
def _get_requests(members):
|
||||
"""Get requests."""
|
||||
commands = list(
|
||||
filter(
|
||||
lambda x: (
|
||||
x[0] not in EXCLUDE and x[0] not in CLIENT_METHODS and callable(x[1])
|
||||
),
|
||||
members,
|
||||
)
|
||||
)
|
||||
commands = {
|
||||
f"client.{c[0]}": Command(
|
||||
f"client.{c[0]}", argspec(c[1]), inspect.getdoc(c[1]), slave=False
|
||||
)
|
||||
for c in commands
|
||||
if not c[0].startswith("_")
|
||||
}
|
||||
return commands
|
||||
|
||||
|
||||
def _get_client_methods(members):
|
||||
"""Get client methods."""
|
||||
commands = list(
|
||||
filter(lambda x: (x[0] not in EXCLUDE and x[0] in CLIENT_METHODS), members)
|
||||
)
|
||||
commands = {
|
||||
f"client.{c[0]}": Command(
|
||||
f"client.{c[0]}", argspec(c[1]), inspect.getdoc(c[1]), slave=False
|
||||
)
|
||||
for c in commands
|
||||
if not c[0].startswith("_")
|
||||
}
|
||||
return commands
|
||||
|
||||
|
||||
def _get_client_properties(members):
|
||||
"""Get client properties."""
|
||||
global CLIENT_ATTRIBUTES # pylint: disable=global-variable-not-assigned
|
||||
commands = list(filter(lambda x: not callable(x[1]), members))
|
||||
commands = {
|
||||
f"client.{c[0]}": Command(f"client.{c[0]}", None, "Read Only!", slave=False)
|
||||
for c in commands
|
||||
if (not c[0].startswith("_") and isinstance(c[1], (str, int, float)))
|
||||
}
|
||||
CLIENT_ATTRIBUTES.extend(list(commands.keys()))
|
||||
return commands
|
||||
|
||||
|
||||
def get_commands(client):
|
||||
"""Retrieve all required methods and attributes.
|
||||
|
||||
Of a client object and convert it to commands.
|
||||
|
||||
:param client: Modbus Client object.
|
||||
:return:
|
||||
"""
|
||||
commands = {}
|
||||
members = inspect.getmembers(client)
|
||||
requests = _get_requests(members)
|
||||
client_methods = _get_client_methods(members)
|
||||
client_attr = _get_client_properties(members)
|
||||
|
||||
result_commands = inspect.getmembers(Result, predicate=predicate)
|
||||
result_commands = {
|
||||
f"result.{c[0]}": Command(f"result.{c[0]}", argspec(c[1]), inspect.getdoc(c[1]))
|
||||
for c in result_commands
|
||||
if (not c[0].startswith("_") and c[0] != "print_result")
|
||||
}
|
||||
commands.update(requests)
|
||||
commands.update(client_methods)
|
||||
commands.update(client_attr)
|
||||
commands.update(result_commands)
|
||||
return commands
|
||||
|
||||
|
||||
class Result:
|
||||
"""Represent result command."""
|
||||
|
||||
function_code: int = None
|
||||
data: Union[Dict[int, Any], Any] = None
|
||||
|
||||
def __init__(self, result):
|
||||
"""Initialize.
|
||||
|
||||
:param result: Response of a modbus command.
|
||||
"""
|
||||
if isinstance(result, dict): # Modbus response
|
||||
self.function_code = result.pop("function_code", None)
|
||||
self.data = dict(result)
|
||||
else:
|
||||
self.data = result
|
||||
|
||||
def decode(self, formatters, byte_order="big", word_order="big"):
|
||||
"""Decode the register response to known formatters.
|
||||
|
||||
:param formatters: int8/16/32/64, uint8/16/32/64, float32/64
|
||||
:param byte_order: little/big
|
||||
:param word_order: little/big
|
||||
"""
|
||||
# Read Holding Registers (3)
|
||||
# Read Input Registers (4)
|
||||
# Read Write Registers (23)
|
||||
if not isinstance(formatters, (list, tuple)):
|
||||
formatters = [formatters]
|
||||
|
||||
if self.function_code not in [3, 4, 23]:
|
||||
print_formatted_text(HTML("<red>Decoder works only for registers!!</red>"))
|
||||
return
|
||||
byte_order = (
|
||||
Endian.LITTLE if byte_order.strip().lower() == "little" else Endian.BIG
|
||||
)
|
||||
word_order = (
|
||||
Endian.LITTLE if word_order.strip().lower() == "little" else Endian.BIG
|
||||
)
|
||||
decoder = BinaryPayloadDecoder.fromRegisters(
|
||||
self.data.get("registers"), byteorder=byte_order, wordorder=word_order
|
||||
)
|
||||
for formatter in formatters:
|
||||
if not (formatter := FORMATTERS.get(formatter)):
|
||||
print_formatted_text(
|
||||
HTML(f"<red>Invalid Formatter - {formatter}!!</red>")
|
||||
)
|
||||
return
|
||||
decoded = getattr(decoder, formatter)()
|
||||
self.print_result(decoded)
|
||||
|
||||
def raw(self):
|
||||
"""Return raw result dict."""
|
||||
self.print_result()
|
||||
|
||||
def _process_dict(self, use_dict):
|
||||
"""Process dict."""
|
||||
new_dict = OrderedDict()
|
||||
for k, v_item in use_dict.items():
|
||||
if isinstance(v_item, bytes):
|
||||
v_item = v_item.decode("utf-8")
|
||||
elif isinstance(v_item, dict):
|
||||
v_item = self._process_dict(v_item)
|
||||
elif isinstance(v_item, (list, tuple)):
|
||||
v_item = [
|
||||
v1.decode("utf-8") if isinstance(v1, bytes) else v1 for v1 in v_item
|
||||
]
|
||||
new_dict[k] = v_item
|
||||
return new_dict
|
||||
|
||||
def print_result(self, data=None):
|
||||
"""Print result object pretty.
|
||||
|
||||
:param data: Data to be printed.
|
||||
"""
|
||||
data = data or self.data
|
||||
if isinstance(data, dict):
|
||||
data = self._process_dict(data)
|
||||
elif isinstance(data, (list, tuple)):
|
||||
data = [v.decode("utf-8") if isinstance(v, bytes) else v for v in data]
|
||||
elif isinstance(data, bytes):
|
||||
data = data.decode("utf-8")
|
||||
tokens = list(pygments.lex(json.dumps(data, indent=4), lexer=JsonLexer()))
|
||||
print_formatted_text(PygmentsTokens(tokens))
|
||||
437
env/lib/python3.11/site-packages/pymodbus/repl/client/main.py
vendored
Normal file
437
env/lib/python3.11/site-packages/pymodbus/repl/client/main.py
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
"""Pymodbus REPL Entry point."""
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
import click
|
||||
from prompt_toolkit import PromptSession, print_formatted_text
|
||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.lexers import PygmentsLexer
|
||||
from prompt_toolkit.styles import Style
|
||||
from pygments.lexers.python import PythonLexer
|
||||
|
||||
from pymodbus import __version__ as pymodbus_version
|
||||
from pymodbus.exceptions import ParameterException
|
||||
from pymodbus.repl.client.completer import (
|
||||
CmdCompleter,
|
||||
has_selected_completion,
|
||||
)
|
||||
from pymodbus.repl.client.helper import CLIENT_ATTRIBUTES, Result
|
||||
from pymodbus.repl.client.mclient import ModbusSerialClient, ModbusTcpClient
|
||||
from pymodbus.transaction import (
|
||||
ModbusAsciiFramer,
|
||||
ModbusBinaryFramer,
|
||||
ModbusRtuFramer,
|
||||
ModbusSocketFramer,
|
||||
)
|
||||
|
||||
|
||||
_logger = logging.getLogger()
|
||||
|
||||
TITLE = rf"""
|
||||
----------------------------------------------------------------------------
|
||||
__________ _____ .___ __________ .__
|
||||
\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | |
|
||||
| ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| |
|
||||
| | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__
|
||||
|____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/
|
||||
\/ \/ \/ \/ \/ \/|__|
|
||||
v1.3.0 - {pymodbus_version}
|
||||
----------------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
|
||||
style = Style.from_dict(
|
||||
{
|
||||
"completion-menu.completion": "bg:#008888 #ffffff",
|
||||
"completion-menu.completion.current": "bg:#00aaaa #000000",
|
||||
"scrollbar.background": "bg:#88aaaa",
|
||||
"scrollbar.button": "bg:#222222",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def bottom_toolbar():
|
||||
"""Do console toolbar.
|
||||
|
||||
:return:
|
||||
"""
|
||||
return HTML(
|
||||
'Press <b><style bg="ansired">CTRL+D or exit </style></b>'
|
||||
' to exit! Type "help" for list of available commands'
|
||||
)
|
||||
|
||||
|
||||
class CaseInsenstiveChoice(click.Choice):
|
||||
"""Do case Insensitive choice for click commands and options."""
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
"""Convert args to uppercase for evaluation."""
|
||||
if value is None:
|
||||
return None
|
||||
return super().convert(value.strip().upper(), param, ctx)
|
||||
|
||||
|
||||
class NumericChoice(click.Choice):
|
||||
"""Do numeric choice for click arguments and options."""
|
||||
|
||||
def __init__(self, choices, typ):
|
||||
"""Initialize."""
|
||||
self.typ = typ
|
||||
super().__init__(choices)
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
"""Convert."""
|
||||
# Exact match
|
||||
if value in self.choices:
|
||||
return self.typ(value)
|
||||
|
||||
if ctx is not None and ctx.token_normalize_func is not None:
|
||||
value = ctx.token_normalize_func(value)
|
||||
for choice in self.casted_choices: # pylint: disable=no-member
|
||||
if ctx.token_normalize_func(choice) == value:
|
||||
return choice
|
||||
|
||||
self.fail(
|
||||
f"invalid choice: {value}. (choose from {', '.join(self.choices)})",
|
||||
param,
|
||||
ctx,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _process_args(args: list, string: bool = True):
|
||||
"""Parse arguments provided on command line.
|
||||
|
||||
:param args: Array of argument values
|
||||
:param string: True if arguments values are strings, false if argument values are integers
|
||||
|
||||
:return Tuple, where the first member is hash of parsed values, and second is boolean flag
|
||||
indicating if parsing succeeded.
|
||||
"""
|
||||
kwargs = {}
|
||||
execute = True
|
||||
skip_index = None
|
||||
|
||||
def _parse_val(arg_name, val):
|
||||
if not string:
|
||||
if "," in val:
|
||||
val = val.split(",")
|
||||
val = [int(v, 0) for v in val]
|
||||
else:
|
||||
val = int(val, 0)
|
||||
kwargs[arg_name] = val
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
if i == skip_index:
|
||||
continue
|
||||
arg = arg.strip()
|
||||
if "=" in arg:
|
||||
arg_name, val = arg.split("=")
|
||||
_parse_val(arg_name, val)
|
||||
else:
|
||||
arg_name, val = arg, args[i + 1]
|
||||
try:
|
||||
_parse_val(arg_name, val)
|
||||
skip_index = i + 1
|
||||
except TypeError:
|
||||
click.secho("Error parsing arguments!", fg="yellow")
|
||||
execute = False
|
||||
break
|
||||
except ValueError:
|
||||
click.secho("Error parsing argument", fg="yellow")
|
||||
execute = False
|
||||
break
|
||||
return kwargs, execute
|
||||
|
||||
|
||||
class CLI: # pylint: disable=too-few-public-methods
|
||||
"""Client definition."""
|
||||
|
||||
def __init__(self, client):
|
||||
"""Set up client and keybindings."""
|
||||
|
||||
use_keys = KeyBindings()
|
||||
history_file = pathlib.Path.home().joinpath(".pymodhis")
|
||||
self.client = client
|
||||
|
||||
@use_keys.add("c-space")
|
||||
def _(event):
|
||||
"""Initialize autocompletion, or select the next completion."""
|
||||
buff = event.app.current_buffer
|
||||
if buff.complete_state:
|
||||
buff.complete_next()
|
||||
else:
|
||||
buff.start_completion(select_first=False)
|
||||
|
||||
@use_keys.add("enter", filter=has_selected_completion)
|
||||
def _(event):
|
||||
"""Make the enter key work as the tab key only when showing the menu."""
|
||||
event.current_buffer.complete_state = None
|
||||
buffer = event.cli.current_buffer
|
||||
buffer.complete_state = None
|
||||
|
||||
self.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")
|
||||
|
||||
def _print_command_help(self, commands):
|
||||
"""Print a list of commands with help text."""
|
||||
for cmd, obj in sorted(commands.items()):
|
||||
if cmd != "help":
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
f"<skyblue>{cmd:45s}</skyblue>"
|
||||
f"<seagreen>{obj.help_text:100s}"
|
||||
"</seagreen>"
|
||||
)
|
||||
)
|
||||
|
||||
def _process_client(self, text, client) -> Result:
|
||||
"""Process client commands."""
|
||||
text = text.strip().split()
|
||||
cmd = text[0].split(".")[1]
|
||||
args = text[1:]
|
||||
kwargs, execute = _process_args(args, string=False)
|
||||
if execute:
|
||||
if text[0] in CLIENT_ATTRIBUTES:
|
||||
result = Result(getattr(client, cmd))
|
||||
else:
|
||||
result = Result(getattr(client, cmd)(**kwargs))
|
||||
result.print_result()
|
||||
return result
|
||||
|
||||
def _process_result(self, text, result):
|
||||
"""Process result commands."""
|
||||
words = text.split()
|
||||
if words[0] == "result.raw":
|
||||
result.raw()
|
||||
if words[0] == "result.decode":
|
||||
args = words[1:]
|
||||
kwargs, execute = _process_args(args)
|
||||
if execute:
|
||||
result.decode(**kwargs)
|
||||
|
||||
def run(self):
|
||||
"""Run the REPL."""
|
||||
result = None
|
||||
while True:
|
||||
try:
|
||||
text = self.session.prompt("> ", complete_while_typing=True)
|
||||
if text.strip().lower() == "help":
|
||||
print_formatted_text(HTML("<u>Available commands:</u>"))
|
||||
self._print_command_help(self.session.completer.commands)
|
||||
elif text.strip().lower() == "exit":
|
||||
raise EOFError()
|
||||
elif text.strip().lower().startswith("client."):
|
||||
result = self._process_client(text, self.client)
|
||||
elif text.strip().lower().startswith("result.") and result:
|
||||
self._process_result(text, result)
|
||||
except KeyboardInterrupt:
|
||||
continue # Control-C pressed. Try again.
|
||||
except EOFError:
|
||||
break # Control-D pressed.
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
click.secho(str(exc), fg="red")
|
||||
|
||||
click.secho("GoodBye!", fg="blue")
|
||||
|
||||
|
||||
@click.group("pymodbus-repl")
|
||||
@click.version_option(str(pymodbus_version), message=TITLE)
|
||||
@click.option("--verbose", is_flag=True, default=False, help="Verbose logs")
|
||||
@click.option(
|
||||
"--broadcast-support",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Support broadcast messages",
|
||||
)
|
||||
@click.option(
|
||||
"--retry-on-empty", is_flag=True, default=False, help="Retry on empty response"
|
||||
)
|
||||
@click.option(
|
||||
"--retry-on-error", is_flag=True, default=False, help="Retry on error response"
|
||||
)
|
||||
@click.option("--retries", default=3, help="Retry count")
|
||||
@click.pass_context
|
||||
def main(
|
||||
ctx,
|
||||
verbose,
|
||||
broadcast_support,
|
||||
retry_on_empty,
|
||||
retry_on_error,
|
||||
retries,
|
||||
):
|
||||
"""Run Main."""
|
||||
if verbose:
|
||||
use_format = (
|
||||
"%(asctime)-15s %(threadName)-15s "
|
||||
"%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s"
|
||||
)
|
||||
logging.basicConfig(format=use_format)
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
ctx.obj = {
|
||||
"broadcast_enable": broadcast_support,
|
||||
"retry_on_empty": retry_on_empty,
|
||||
"retry_on_invalid": retry_on_error,
|
||||
"retries": retries,
|
||||
}
|
||||
|
||||
|
||||
@main.command("tcp")
|
||||
@click.pass_context
|
||||
@click.option("--host", default="localhost", help="Modbus TCP IP ")
|
||||
@click.option(
|
||||
"--port",
|
||||
default=502,
|
||||
type=int,
|
||||
help="Modbus TCP port",
|
||||
)
|
||||
@click.option(
|
||||
"--framer",
|
||||
default="tcp",
|
||||
type=str,
|
||||
help="Override the default packet framer tcp|rtu",
|
||||
)
|
||||
def tcp(ctx, host, port, framer):
|
||||
"""Define TCP."""
|
||||
kwargs = {"host": host, "port": port}
|
||||
kwargs.update(**ctx.obj)
|
||||
if framer == "rtu":
|
||||
kwargs["framer"] = ModbusRtuFramer
|
||||
client = ModbusTcpClient(**kwargs)
|
||||
cli = CLI(client)
|
||||
cli.run()
|
||||
|
||||
|
||||
@main.command("serial")
|
||||
@click.pass_context
|
||||
@click.option(
|
||||
"--method",
|
||||
default="rtu",
|
||||
type=str,
|
||||
help="Modbus Serial Mode (rtu/ascii)",
|
||||
)
|
||||
@click.option(
|
||||
"--port",
|
||||
default=None,
|
||||
type=str,
|
||||
help="Modbus RTU port",
|
||||
)
|
||||
@click.option(
|
||||
"--baudrate",
|
||||
help="Modbus RTU serial baudrate to use.",
|
||||
default=9600,
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--bytesize",
|
||||
help="Modbus RTU serial Number of data bits. "
|
||||
"Possible values: FIVEBITS, SIXBITS, SEVENBITS, "
|
||||
"EIGHTBITS.",
|
||||
type=NumericChoice(["5", "6", "7", "8"], int),
|
||||
default="8",
|
||||
)
|
||||
@click.option(
|
||||
"--parity",
|
||||
help="Modbus RTU serial parity. "
|
||||
" Enable parity checking. Possible values: "
|
||||
"PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, "
|
||||
'PARITY_SPACE. Default to "N"',
|
||||
default="N",
|
||||
type=CaseInsenstiveChoice(["N", "E", "O", "M", "S"]),
|
||||
)
|
||||
@click.option(
|
||||
"--stopbits",
|
||||
help="Modbus RTU serial stop bits. "
|
||||
"Number of stop bits. Possible values: STOPBITS_ONE, "
|
||||
'STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to "1"',
|
||||
default="1",
|
||||
type=NumericChoice(["1", "1.5", "2"], float),
|
||||
)
|
||||
@click.option(
|
||||
"--xonxoff",
|
||||
help="Modbus RTU serial xonxoff. Enable software flow control.",
|
||||
default=0,
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--rtscts",
|
||||
help="Modbus RTU serial rtscts. Enable hardware (RTS/CTS) flow " "control.",
|
||||
default=0,
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--dsrdtr",
|
||||
help="Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) flow " "control.",
|
||||
default=0,
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--timeout",
|
||||
help="Modbus RTU serial read timeout.",
|
||||
default=0.25,
|
||||
type=float,
|
||||
)
|
||||
@click.option(
|
||||
"--write-timeout",
|
||||
help="Modbus RTU serial write timeout.",
|
||||
default=2,
|
||||
type=float,
|
||||
)
|
||||
def serial( # pylint: disable=too-many-arguments
|
||||
ctx,
|
||||
method,
|
||||
port,
|
||||
baudrate,
|
||||
bytesize,
|
||||
parity,
|
||||
stopbits,
|
||||
xonxoff,
|
||||
rtscts,
|
||||
dsrdtr,
|
||||
timeout,
|
||||
write_timeout,
|
||||
):
|
||||
"""Define serial communication."""
|
||||
method = method.lower()
|
||||
if method == "ascii":
|
||||
framer = ModbusAsciiFramer
|
||||
elif method == "rtu":
|
||||
framer = ModbusRtuFramer
|
||||
elif method == "binary":
|
||||
framer = ModbusBinaryFramer
|
||||
elif method == "socket":
|
||||
framer = ModbusSocketFramer
|
||||
else:
|
||||
raise ParameterException("Invalid framer method requested")
|
||||
client = ModbusSerialClient(
|
||||
framer=framer,
|
||||
port=port,
|
||||
baudrate=baudrate,
|
||||
bytesize=bytesize,
|
||||
parity=parity,
|
||||
stopbits=stopbits,
|
||||
xonxoff=xonxoff,
|
||||
rtscts=rtscts,
|
||||
dsrdtr=dsrdtr,
|
||||
timeout=timeout,
|
||||
write_timeout=write_timeout,
|
||||
**ctx.obj,
|
||||
)
|
||||
cli = CLI(client)
|
||||
cli.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # pylint: disable=no-value-for-parameter
|
||||
683
env/lib/python3.11/site-packages/pymodbus/repl/client/mclient.py
vendored
Normal file
683
env/lib/python3.11/site-packages/pymodbus/repl/client/mclient.py
vendored
Normal file
@@ -0,0 +1,683 @@
|
||||
"""Modbus Clients to be used with REPL."""
|
||||
# pylint: disable=missing-type-doc
|
||||
import functools
|
||||
|
||||
from pymodbus.client import ModbusSerialClient as _ModbusSerialClient
|
||||
from pymodbus.client import ModbusTcpClient as _ModbusTcpClient
|
||||
from pymodbus.diag_message import (
|
||||
ChangeAsciiInputDelimiterRequest,
|
||||
ClearCountersRequest,
|
||||
ClearOverrunCountRequest,
|
||||
ForceListenOnlyModeRequest,
|
||||
GetClearModbusPlusRequest,
|
||||
RestartCommunicationsOptionRequest,
|
||||
ReturnBusCommunicationErrorCountRequest,
|
||||
ReturnBusExceptionErrorCountRequest,
|
||||
ReturnBusMessageCountRequest,
|
||||
ReturnDiagnosticRegisterRequest,
|
||||
ReturnIopOverrunCountRequest,
|
||||
ReturnQueryDataRequest,
|
||||
ReturnSlaveBusCharacterOverrunCountRequest,
|
||||
ReturnSlaveBusyCountRequest,
|
||||
ReturnSlaveMessageCountRequest,
|
||||
ReturnSlaveNAKCountRequest,
|
||||
ReturnSlaveNoResponseCountRequest,
|
||||
)
|
||||
from pymodbus.exceptions import ModbusIOException
|
||||
from pymodbus.mei_message import ReadDeviceInformationRequest
|
||||
from pymodbus.other_message import (
|
||||
GetCommEventCounterRequest,
|
||||
GetCommEventLogRequest,
|
||||
ReadExceptionStatusRequest,
|
||||
ReportSlaveIdRequest,
|
||||
)
|
||||
from pymodbus.pdu import ExceptionResponse, ModbusExceptions
|
||||
|
||||
|
||||
def make_response_dict(resp):
|
||||
"""Make response dict."""
|
||||
resp_dict = {"function_code": resp.function_code, "address": resp.address}
|
||||
if hasattr(resp, "value"):
|
||||
resp_dict["value"] = resp.value
|
||||
elif hasattr(resp, "values"):
|
||||
resp_dict["values"] = resp.values
|
||||
elif hasattr(resp, "count"):
|
||||
resp_dict["count"] = resp.count
|
||||
return resp_dict
|
||||
|
||||
|
||||
def handle_brodcast(func):
|
||||
"""Handle broadcast."""
|
||||
|
||||
@functools.wraps(func)
|
||||
def _wrapper(*args, **kwargs):
|
||||
self = args[0]
|
||||
resp = func(*args, **kwargs)
|
||||
if not kwargs.get("slave") and self.params.broadcast_enable:
|
||||
return {"broadcasted": True}
|
||||
if not resp.isError():
|
||||
return make_response_dict(resp)
|
||||
return ExtendedRequestSupport._process_exception( # pylint: disable=protected-access
|
||||
resp, **kwargs
|
||||
)
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
class ExtendedRequestSupport: # pylint: disable=(too-many-public-methods
|
||||
"""Extended request support."""
|
||||
|
||||
@staticmethod
|
||||
def _process_exception(resp, **kwargs):
|
||||
"""Set internal process exception."""
|
||||
if "slave" not in kwargs:
|
||||
err = {"message": "Broadcast message, ignoring errors!!!"}
|
||||
else:
|
||||
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, slave=0, **kwargs):
|
||||
"""Read `count` coils from a given slave starting at `address`.
|
||||
|
||||
:param address: The starting address to read from
|
||||
:param count: The number of coils to read
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:returns: List of register values
|
||||
"""
|
||||
resp = super().read_coils( # pylint: disable=no-member
|
||||
address, count, slave, **kwargs
|
||||
)
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "bits": resp.bits}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def read_discrete_inputs(self, address, count=1, slave=0, **kwargs):
|
||||
"""Read `count` number of discrete inputs starting at offset `address`.
|
||||
|
||||
:param address: The starting address to read from
|
||||
:param count: The number of coils to read
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return: List of bits
|
||||
"""
|
||||
resp = super().read_discrete_inputs( # pylint: disable=no-member
|
||||
address, count, slave, **kwargs
|
||||
)
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "bits": resp.bits}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
@handle_brodcast
|
||||
def write_coil(self, address, value, slave=0, **kwargs):
|
||||
"""Write `value` to coil at `address`.
|
||||
|
||||
:param address: coil offset to write to
|
||||
:param value: bit value to write
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().write_coil( # pylint: disable=no-member
|
||||
address, value, slave, **kwargs
|
||||
)
|
||||
return resp
|
||||
|
||||
@handle_brodcast
|
||||
def write_coils(self, address, values, slave=0, **kwargs):
|
||||
"""Write `value` to coil at `address`.
|
||||
|
||||
:param address: coil offset to write to
|
||||
:param values: list of bit values to write (comma separated)
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().write_coils( # pylint: disable=no-member
|
||||
address, values, slave, **kwargs
|
||||
)
|
||||
return resp
|
||||
|
||||
@handle_brodcast
|
||||
def write_register(self, address, value, slave=0, **kwargs):
|
||||
"""Write `value` to register at `address`.
|
||||
|
||||
:param address: register offset to write to
|
||||
:param value: register value to write
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().write_register( # pylint: disable=no-member
|
||||
address, value, slave, **kwargs
|
||||
)
|
||||
return resp
|
||||
|
||||
@handle_brodcast
|
||||
def write_registers(self, address, values, slave=0, **kwargs):
|
||||
"""Write list of `values` to registers starting at `address`.
|
||||
|
||||
:param address: register offset to write to
|
||||
:param values: list of register value to write (comma separated)
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().write_registers( # pylint: disable=no-member
|
||||
address, values, slave, **kwargs
|
||||
)
|
||||
return resp
|
||||
|
||||
def read_holding_registers(self, address, count=1, slave=0, **kwargs):
|
||||
"""Read `count` number of holding registers starting at `address`.
|
||||
|
||||
:param address: starting register offset to read from
|
||||
:param count: Number of registers to read
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().read_holding_registers( # pylint: disable=no-member
|
||||
address, count, slave, **kwargs
|
||||
)
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "registers": resp.registers}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def read_input_registers(self, address, count=1, slave=0, **kwargs):
|
||||
"""Read `count` number of input registers starting at `address`.
|
||||
|
||||
:param address: starting register offset to read from to
|
||||
:param count: Number of registers to read
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().read_input_registers( # pylint: disable=no-member
|
||||
address, count, slave, **kwargs
|
||||
)
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "registers": resp.registers}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def readwrite_registers(
|
||||
self,
|
||||
read_address=0,
|
||||
read_count=0,
|
||||
write_address=0,
|
||||
values=0,
|
||||
slave=0,
|
||||
**kwargs,
|
||||
):
|
||||
"""Read `read_count` number of holding registers.
|
||||
|
||||
Starting at `read_address`
|
||||
and write `write_registers` starting at `write_address`.
|
||||
|
||||
:param read_address: register offset to read from
|
||||
:param read_count: Number of registers to read
|
||||
:param write_address: register offset to write to
|
||||
:param values: List of register values to write (comma separated)
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().readwrite_registers( # pylint: disable=no-member
|
||||
read_address=read_address,
|
||||
read_count=read_count,
|
||||
write_address=write_address,
|
||||
values=values,
|
||||
slave=slave,
|
||||
**kwargs,
|
||||
)
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "registers": resp.registers}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def mask_write_register(
|
||||
self,
|
||||
address=0x0000,
|
||||
and_mask=0xFFFF,
|
||||
or_mask=0x0000,
|
||||
slave=0,
|
||||
**kwargs,
|
||||
):
|
||||
"""Mask content of holding register at `address` with `and_mask` and `or_mask`.
|
||||
|
||||
:param address: Reference address of register
|
||||
:param and_mask: And Mask
|
||||
:param or_mask: OR Mask
|
||||
:param slave: Modbus slave slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
resp = super().mask_write_register( # pylint: disable=no-member
|
||||
address=address, and_mask=and_mask, or_mask=or_mask, slave=slave, **kwargs
|
||||
)
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function_code": resp.function_code,
|
||||
"address": resp.address,
|
||||
"and mask": resp.and_mask,
|
||||
"or mask": resp.or_mask,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def read_device_information(self, read_code=None, object_id=0x00, **kwargs):
|
||||
"""Read the identification and additional information of remote slave.
|
||||
|
||||
:param read_code: Read Device ID code (0x01/0x02/0x03/0x04)
|
||||
:param object_id: Identification of the first object to obtain.
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReadDeviceInformationRequest(read_code, object_id, **kwargs)
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function_code": resp.function_code,
|
||||
"information": resp.information,
|
||||
"object count": resp.number_of_objects,
|
||||
"conformity": resp.conformity,
|
||||
"next object id": resp.next_object_id,
|
||||
"more follows": resp.more_follows,
|
||||
"space left": resp.space_left,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=request.slave_id)
|
||||
|
||||
def report_slave_id(self, slave=0, **kwargs):
|
||||
"""Report information about remote slave ID.
|
||||
|
||||
:param slave: Modbus slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReportSlaveIdRequest(slave, **kwargs)
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function_code": resp.function_code,
|
||||
"identifier": resp.identifier.decode("cp1252"),
|
||||
"status": resp.status,
|
||||
"byte count": resp.byte_count,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=slave)
|
||||
|
||||
def read_exception_status(self, slave=0, **kwargs):
|
||||
"""Read contents of eight Exception Status output in a remote device.
|
||||
|
||||
:param slave: Modbus slave ID
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReadExceptionStatusRequest(slave, **kwargs)
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {"function_code": resp.function_code, "status": resp.status}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=request.slave_id)
|
||||
|
||||
def get_com_event_counter(self, **kwargs):
|
||||
"""Read status word and an event count.
|
||||
|
||||
From the remote device's communication event counter.
|
||||
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = GetCommEventCounterRequest(**kwargs)
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function_code": resp.function_code,
|
||||
"status": resp.status,
|
||||
"count": resp.count,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=request.slave_id)
|
||||
|
||||
def get_com_event_log(self, **kwargs):
|
||||
"""Read status word.
|
||||
|
||||
Event count, message count, and a field of event
|
||||
bytes from the remote device.
|
||||
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = GetCommEventLogRequest(**kwargs)
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function_code": resp.function_code,
|
||||
"status": resp.status,
|
||||
"message count": resp.message_count,
|
||||
"event count": resp.event_count,
|
||||
"events": resp.events,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=request.slave_id)
|
||||
|
||||
def _execute_diagnostic_request(self, request):
|
||||
"""Execute diagnostic request."""
|
||||
resp = self.execute(request) # pylint: disable=no-member
|
||||
if not resp.isError():
|
||||
return {
|
||||
"function code": resp.function_code,
|
||||
"sub function code": resp.sub_function_code,
|
||||
"message": resp.message,
|
||||
}
|
||||
return ExtendedRequestSupport._process_exception(resp, slave=request.slave_id)
|
||||
|
||||
def return_query_data(self, message=0, **kwargs):
|
||||
"""Loop back data sent in response.
|
||||
|
||||
:param message: Message to be looped back
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnQueryDataRequest(message, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def restart_comm_option(self, toggle=False, **kwargs):
|
||||
"""Initialize and restart remote devices.
|
||||
|
||||
Serial interface and clear all of its communications event counters.
|
||||
|
||||
:param toggle: Toggle Status [ON(0xff00)/OFF(0x0000]
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = RestartCommunicationsOptionRequest(toggle, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_diagnostic_register(self, data=0, **kwargs):
|
||||
"""Read 16-bit diagnostic register.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnDiagnosticRegisterRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def change_ascii_input_delimiter(self, data=0, **kwargs):
|
||||
"""Change message delimiter for future requests.
|
||||
|
||||
:param data: New delimiter character
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ChangeAsciiInputDelimiterRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def force_listen_only_mode(self, data=0, **kwargs):
|
||||
"""Force addressed remote device to its Listen Only Mode.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ForceListenOnlyModeRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def clear_counters(self, data=0, **kwargs):
|
||||
"""Clear all counters and diag registers.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ClearCountersRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_bus_message_count(self, data=0, **kwargs):
|
||||
"""Return count of message detected on bus by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnBusMessageCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_bus_com_error_count(self, data=0, **kwargs):
|
||||
"""Return count of CRC errors received by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnBusCommunicationErrorCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_bus_exception_error_count(self, data=0, **kwargs):
|
||||
"""Return count of Modbus exceptions returned by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnBusExceptionErrorCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_slave_message_count(self, data=0, **kwargs):
|
||||
"""Return count of messages addressed to remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnSlaveMessageCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_slave_no_response_count(self, data=0, **kwargs):
|
||||
"""Return count of No responses by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnSlaveNoResponseCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_slave_no_ack_count(self, data=0, **kwargs):
|
||||
"""Return count of NO ACK exceptions sent by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnSlaveNAKCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_slave_busy_count(self, data=0, **kwargs):
|
||||
"""Return count of server busy exceptions sent by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnSlaveBusyCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_slave_bus_char_overrun_count(self, data=0, **kwargs):
|
||||
"""Return count of messages not handled.
|
||||
|
||||
By remote slave due to character overrun condition.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnSlaveBusCharacterOverrunCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def return_iop_overrun_count(self, data=0, **kwargs):
|
||||
"""Return count of iop overrun errors by remote slave.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ReturnIopOverrunCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def clear_overrun_count(self, data=0, **kwargs):
|
||||
"""Clear over run counter.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = ClearOverrunCountRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
def get_clear_modbus_plus(self, data=0, **kwargs):
|
||||
"""Get/clear stats of remote modbus plus device.
|
||||
|
||||
:param data: Data field (0x0000)
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request = GetClearModbusPlusRequest(data, **kwargs)
|
||||
return self._execute_diagnostic_request(request)
|
||||
|
||||
|
||||
class ModbusSerialClient(ExtendedRequestSupport, _ModbusSerialClient):
|
||||
"""Modbus serial client."""
|
||||
|
||||
def __init__(self, framer, **kwargs):
|
||||
"""Initialize."""
|
||||
super().__init__(framer=framer, **kwargs)
|
||||
|
||||
def get_port(self):
|
||||
"""Get serial Port.
|
||||
|
||||
:return: Current Serial port
|
||||
"""
|
||||
return self.comm_params.port
|
||||
|
||||
def set_port(self, value):
|
||||
"""Set serial Port setter.
|
||||
|
||||
:param value: New port
|
||||
"""
|
||||
self.comm_params.port = value
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_stopbits(self):
|
||||
"""Get number of stop bits.
|
||||
|
||||
:return: Current Stop bits
|
||||
"""
|
||||
return self.params.stopbits
|
||||
|
||||
def set_stopbits(self, value):
|
||||
"""Set stop bit.
|
||||
|
||||
:param value: Possible values (1, 1.5, 2)
|
||||
"""
|
||||
self.params.stopbits = float(value)
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_bytesize(self):
|
||||
"""Get number of data bits.
|
||||
|
||||
:return: Current bytesize
|
||||
"""
|
||||
return self.comm_params.bytesize
|
||||
|
||||
def set_bytesize(self, value):
|
||||
"""Set Byte size.
|
||||
|
||||
:param value: Possible values (5, 6, 7, 8)
|
||||
|
||||
"""
|
||||
self.comm_params.bytesize = int(value)
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_parity(self):
|
||||
"""Enable Parity Checking.
|
||||
|
||||
:return: Current parity setting
|
||||
"""
|
||||
return self.params.parity
|
||||
|
||||
def set_parity(self, value):
|
||||
"""Set parity Setter.
|
||||
|
||||
:param value: Possible values ("N", "E", "O", "M", "S")
|
||||
"""
|
||||
self.params.parity = value
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_baudrate(self):
|
||||
"""Get serial Port baudrate.
|
||||
|
||||
:return: Current baudrate
|
||||
"""
|
||||
return self.comm_params.baudrate
|
||||
|
||||
def set_baudrate(self, value):
|
||||
"""Set baudrate setter.
|
||||
|
||||
:param value: <supported baudrate>
|
||||
"""
|
||||
self.comm_params.baudrate = int(value)
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_timeout(self):
|
||||
"""Get serial Port Read timeout.
|
||||
|
||||
:return: Current read imeout.
|
||||
"""
|
||||
return self.comm_params.timeout_connect
|
||||
|
||||
def set_timeout(self, value):
|
||||
"""Read timeout setter.
|
||||
|
||||
:param value: Read Timeout in seconds
|
||||
"""
|
||||
self.comm_params.timeout_connect = float(value)
|
||||
if self.is_socket_open():
|
||||
self.close()
|
||||
|
||||
def get_serial_settings(self):
|
||||
"""Get Current Serial port settings.
|
||||
|
||||
:return: Current Serial settings as dict.
|
||||
"""
|
||||
return {
|
||||
"baudrate": self.comm_params.baudrate,
|
||||
"port": self.comm_params.port,
|
||||
"parity": self.comm_params.parity,
|
||||
"stopbits": self.comm_params.stopbits,
|
||||
"bytesize": self.comm_params.bytesize,
|
||||
"read timeout": self.comm_params.timeout_connect,
|
||||
"t1.5": self.inter_char_timeout,
|
||||
"t3.5": self.silent_interval,
|
||||
}
|
||||
|
||||
|
||||
class ModbusTcpClient(ExtendedRequestSupport, _ModbusTcpClient):
|
||||
"""TCP client."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize."""
|
||||
super().__init__(**kwargs)
|
||||
Reference in New Issue
Block a user