Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Update repl to work with latest pymodbus on dev branch #26

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1,083 changes: 549 additions & 534 deletions poetry.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions pymodbus_repl/client/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from pygments.lexers.python import PythonLexer
from pymodbus import __version__ as pymodbus_version
from pymodbus.exceptions import ParameterException
from pymodbus.transaction import (
ModbusAsciiFramer,
ModbusRtuFramer,
ModbusSocketFramer,
from pymodbus.framer import (
FramerAscii,
FramerRTU,
FramerSocket,
)

from pymodbus_repl import __VERSION__ as repl_version
Expand Down Expand Up @@ -261,7 +261,7 @@ def tcp(ctx, host, port, framer):
kwargs = {"host": host, "port": port}
kwargs.update(**ctx.obj)
if framer == "rtu":
kwargs["framer"] = ModbusRtuFramer
kwargs["framer"] = FramerRTU
client = ModbusTcpClient(**kwargs)
cli = CLI(client)
cli.run()
Expand Down Expand Up @@ -359,11 +359,11 @@ def serial( # pylint: disable=too-many-arguments
"""Define serial communication."""
method = method.lower()
if method == "ascii":
framer = ModbusAsciiFramer
framer = FramerAscii
elif method == "rtu":
framer = ModbusRtuFramer
framer = FramerRTU
elif method == "socket":
framer = ModbusSocketFramer
framer = FramerSocket
else:
raise ParameterException("Invalid framer method requested")
client = ModbusSerialClient(
Expand Down
87 changes: 43 additions & 44 deletions pymodbus_repl/client/mclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
from pymodbus.client import ModbusTcpClient as _ModbusTcpClient
from pymodbus.client.base import ModbusBaseSyncClient as _ModbusBaseSyncClient
from pymodbus.exceptions import ModbusIOException
from pymodbus.pdu import ExceptionResponse, ModbusExceptions
from pymodbus.pdu import ExceptionResponse
from pymodbus.pdu.diag_message import (
ChangeAsciiInputDelimiterRequest,
ClearCountersRequest,
ClearOverrunCountRequest,
DiagnosticStatusResponse,
DiagnosticBase,
ForceListenOnlyModeRequest,
GetClearModbusPlusRequest,
RestartCommunicationsOptionRequest,
Expand Down Expand Up @@ -42,7 +42,7 @@
ReportSlaveIdRequest,
ReportSlaveIdResponse,
)
from pymodbus.pdu.register_write_message import MaskWriteRegisterResponse
from pymodbus.pdu.register_message import MaskWriteRegisterResponse


def make_response_dict(resp):
Expand Down Expand Up @@ -74,34 +74,33 @@ def _wrapper(*args, **kwargs):

return _wrapper


if TYPE_CHECKING:
_Base = _ModbusBaseSyncClient
else:
_Base = object
class ExtendedRequestSupport(_Base): # pylint: disable=(too-many-public-methods


class ExtendedRequestSupport(_Base): # pylint: disable=too-many-public-methods
"""Extended request support."""

@staticmethod
def _process_exception(resp, **kwargs):
def _process_exception(resp, **kwargs) -> dict:
"""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
return {"message": "Broadcast message, ignoring errors!!!"}
if isinstance(resp, ExceptionResponse):
return {
"original_function_code": f"{resp.function_code - 0x80} ({hex(resp.function_code - 0x80)})",
"error_function_code": f"{resp.function_code} ({hex(resp.function_code)})",
"exception code": resp.exception_code,
}
if isinstance(resp, ModbusIOException):
return {
"original_function_code": f"{resp.fcode} ({hex(resp.fcode)})",
"error": resp.message,
}
return {"error": str(resp)}

def read_coils(self, address, count=1, slave=0, **kwargs):
"""Read `count` coils from a given slave starting at `address`.
Expand All @@ -113,7 +112,7 @@ def read_coils(self, address, count=1, slave=0, **kwargs):
:returns: List of register values
"""
resp = super().read_coils(
address, count, slave, **kwargs
address, count=count, slave=slave, **kwargs
)
if not resp.isError():
return {"function_code": resp.function_code, "bits": resp.bits}
Expand All @@ -129,7 +128,7 @@ def read_discrete_inputs(self, address, count=1, slave=0, **kwargs):
:return: List of bits
"""
resp = super().read_discrete_inputs(
address, count, slave, **kwargs
address, count=count, slave=slave, **kwargs
)
if not resp.isError():
return {"function_code": resp.function_code, "bits": resp.bits}
Expand All @@ -146,7 +145,7 @@ def write_coil(self, address, value, slave=0, **kwargs):
:return:
"""
resp = super().write_coil(
address, value, slave, **kwargs
address, value, slave=slave, **kwargs
)
return resp

Expand All @@ -161,7 +160,7 @@ def write_coils(self, address, values, slave=0, **kwargs):
:return:
"""
resp = super().write_coils(
address, values, slave, **kwargs
address, values, slave=slave, **kwargs
)
return resp

Expand All @@ -176,7 +175,7 @@ def write_register(self, address, value, slave=0, **kwargs):
:return:
"""
resp = super().write_register(
address, value, slave, **kwargs
address, value, slave=slave, **kwargs
)
return resp

Expand All @@ -191,7 +190,7 @@ def write_registers(self, address, values, slave=0, **kwargs):
:return:
"""
resp = super().write_registers(
address, values, slave, **kwargs
address, values, slave=slave, **kwargs
)
return resp

Expand All @@ -205,7 +204,7 @@ def read_holding_registers(self, address, count=1, slave=0, **kwargs):
:return:
"""
resp = super().read_holding_registers(
address, count, slave, **kwargs
address, count=count, slave=slave, **kwargs
)
if not resp.isError():
return {"function_code": resp.function_code, "registers": resp.registers}
Expand All @@ -221,20 +220,20 @@ def read_input_registers(self, address, count=1, slave=0, **kwargs):
:return:
"""
resp = super().read_input_registers(
address, count, slave, **kwargs
address, count=count, slave=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,
self,
read_address=0,
read_count=0,
write_address=0,
values=0,
slave=0,
**kwargs,
):
"""Read `read_count` number of holding registers.

Expand Down Expand Up @@ -262,12 +261,12 @@ def readwrite_registers(
return ExtendedRequestSupport._process_exception(resp, slave=slave)

def mask_write_register(
self,
address=0x0000,
and_mask=0xFFFF,
or_mask=0x0000,
slave=0,
**kwargs,
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`.

Expand Down Expand Up @@ -384,7 +383,7 @@ def get_com_event_log(self, **kwargs):

def _execute_diagnostic_request(self, request):
"""Execute diagnostic request."""
resp: DiagnosticStatusResponse = self.execute(request)
resp: DiagnosticBase = self.execute(request)
if not resp.isError():
return {
"function code": resp.function_code,
Expand Down
4 changes: 2 additions & 2 deletions pymodbus_repl/lib/completer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Command Completion for pymodbus REPL. """
"""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 pymodbus_repl.lib.helper import get_commands, Command
from pymodbus_repl.lib.helper import Command, get_commands


@Condition
Expand Down
3 changes: 2 additions & 1 deletion pymodbus_repl/lib/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from prompt_toolkit.formatted_text import HTML, PygmentsTokens
from prompt_toolkit.styles import Style
from pygments.lexers.data import JsonLexer
from pymodbus.payload import BinaryPayloadDecoder, Endian
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder


predicate = inspect.isfunction
Expand Down
38 changes: 16 additions & 22 deletions pymodbus_repl/lib/reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@
ModbusSparseDataBlock,
)
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.framer import (
FramerAscii,
FramerRTU,
FramerSocket,
FramerTLS,
)
from pymodbus.logging import Log
from pymodbus.pdu import ExceptionResponse, ModbusExceptions
from pymodbus.server.async_io import (
from pymodbus.pdu import ExceptionResponse
from pymodbus.server import (
ModbusSerialServer,
ModbusTcpServer,
ModbusTlsServer,
ModbusUdpServer,
)
from pymodbus.transaction import (
ModbusAsciiFramer,
ModbusRtuFramer,
ModbusSocketFramer,
ModbusTlsFramer,
)


SERVER_MAPPER = {
Expand All @@ -51,17 +51,17 @@
}

DEFAULT_FRAMER = {
"tcp": ModbusSocketFramer,
"rtu": ModbusRtuFramer,
"tls": ModbusTlsFramer,
"udp": ModbusSocketFramer,
"ascii": ModbusAsciiFramer
"tcp": FramerSocket,
"rtu": FramerRTU,
"tls": FramerTLS,
"udp": FramerSocket,
"ascii": FramerAscii
}

DEFAULT_MANIPULATOR = {
"response_type": "normal", # normal, error, delayed, empty
"delay_by": 0,
"error_code": ModbusExceptions.IllegalAddress,
"error_code": ExceptionResponse.ILLEGAL_ADDRESS,
"clear_after": 5, # request count
}
DEFAULT_MODBUS_MAP = {
Expand Down Expand Up @@ -149,7 +149,6 @@ def __init__(
coils: BaseModbusDataBlock,
input_registers: BaseModbusDataBlock,
holding_registers: BaseModbusDataBlock,
zero_mode: bool = False,
randomize: int = 0,
change_rate: int = 0,
**kwargs,
Expand All @@ -159,7 +158,6 @@ def __init__(
:param coils: Coils data block
:param input_registers: Input registers data block
:param holding_registers: Holding registers data block
:param zero_mode: Enable zero mode for data blocks
:param randomize: Randomize reads every <n> reads for DI and IR,
default is disabled (0)
:param change_rate: Rate in % of registers to change for DI and IR,
Expand All @@ -173,8 +171,7 @@ def __init__(
di=discrete_inputs,
co=coils,
ir=input_registers,
hr=holding_registers,
zero_mode=zero_mode,
hr=holding_registers
)
min_binary_value = kwargs.get("min_binary_value", 0)
max_binary_value = kwargs.get("max_binary_value", 1)
Expand All @@ -200,8 +197,6 @@ def getValues(self, fc_as_hex, address, count=1):
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
"""
if not self.zero_mode:
address += 1
Log.debug("getValues: fc-[{}] address-{}: count-{}", fc_as_hex, address, count)
_block_type = self.decode(fc_as_hex)
if self._randomize > 0 and _block_type in {"d", "i"}:
Expand Down Expand Up @@ -463,7 +458,6 @@ def create_context(
**block,
randomize=randomize,
change_rate=change_rate,
zero_mode=True,
**data_block_settings,
)
if not single:
Expand All @@ -490,7 +484,7 @@ def factory( # pylint: disable=dangerous-default-value,too-many-arguments
):
"""Create ReactiveModbusServer.
:param server: Modbus server type (tcp, rtu, tls, udp)
:param framer: Modbus framer (ModbusSocketFramer, ModbusRTUFramer, ModbusTLSFramer)
:param framer: Modbus framer (FramerSocket, FramerRTU, FramerTLS)
:param context: Modbus server context to use
:param slave: Modbus slave id
:param single: Run in single mode
Expand Down
4 changes: 2 additions & 2 deletions pymodbus_repl/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import typer
from pymodbus import pymodbus_apply_logging_config
from pymodbus.framer import FramerType
from pymodbus.logging import Log
from pymodbus.transaction import ModbusSocketFramer
from typing_extensions import Annotated

from pymodbus_repl.lib.reactive import (
Expand Down Expand Up @@ -165,7 +165,7 @@ def run(
# TBD extra_args = ctx.args
web_app_config = ctx.obj
loop = asyncio.get_event_loop()
framer = DEFAULT_FRAMER.get(modbus_framer, ModbusSocketFramer)
framer = DEFAULT_FRAMER.get(modbus_framer, FramerType.SOCKET)
if modbus_config_path:
with open(modbus_config_path, encoding="utf-8") as my_file:
modbus_config = json.load(my_file)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ version=">=3.8.6"
include = ["pymodbus_repl"]

[tool.poetry.group.dev.dependencies]
pymodbus = {git = "https://github.com/pymodbus-dev/pymodbus", tag = "v3.7.0"}
ruff = "^0.5.6"
coverage = "^7.4.1"
pytest-xdist = "^3.5.0"
Expand All @@ -37,6 +36,7 @@ twine = "^5.0.0"
mypy = "^1.11.1"
types-pygments = "^2.18.0.20240506"
types-tabulate = "^0.9.0.20240106"
pymodbus = {git = "https://github.com/pymodbus-dev/pymodbus.git", rev = "dev"}

[build-system]
requires = ["poetry-core"]
Expand Down
Loading