diff --git a/.gitignore b/.gitignore index 68bc17f..2dc53ca 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/README.md b/README.md index d719e58..79a4b68 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Pymodbus-REPL is a REPL (Read-Eval-Print Loop) tool for working with Modbus devi - Python 3.8+ - Poetry (installed globally or within a virtual environment) -### Installation +### Dev instructions 1. Clone the repository: @@ -23,6 +23,80 @@ Pymodbus-REPL is a REPL (Read-Eval-Print Loop) tool for working with Modbus devi `poetry install` +**NOTE** This repo is meant to be an helper for [pymodbus](https://github.com/pymodbus-dev/pymodbus) and the usage requires +a working version of pymodbus. + +The installed `pymodbus` library for local development can also have impact on the path resolution while working locally on this repo. +To overcome that problem, please make sure to run the [client](./pymodbus/client/main.py) and [server](./pymodbus/server/main.py) files +from with in the respective working directories. + +For .e.g + +#### Run Server +``` +(pymodbus3.8) +pymodbus/repl/server on  repl-server-startup [!?] via 🐍 v3.8.13 (pymodbus3.8) +❯ python3 main.py --host 0.0.0.0 --verbose run --modbus-config default_config.json --modbus-server tcp --modbus-framer socket --modbus-port 5020 --unit-id 1 --unit-id 2 -u 4 -r 1 --timeout 2 +2024-02-17 13:27:17,218 INFO logging:97 Modbus server started +2024-02-17 13:27:17,219 DEBUG logging:103 Awaiting connections server_listener +2024-02-17 13:27:17,219 INFO logging:97 Server listening. + +__________ .______. _________ +\______ \___.__. _____ ____ __| _/\_ |__ __ __ ______ / _____/ ______________ __ ___________ + | ___< | |/ \ / _ \ / __ | | __ \| | \/ ___/ \_____ \_/ __ \_ __ \ \/ // __ \_ __ \\ + | | \___ | Y Y ( <_> ) /_/ | | \_\ \ | /\___ \ / \ ___/| | \/\ /\ ___/| | \/ + |____| / ____|__|_| /\____/\____ | |___ /____//____ > /_______ /\___ >__| \_/ \___ >__| + \/ \/ \/ \/ \/ \/ \/ \/ + + +SERVER > +``` + +#### Run client +``` +pymodbus/repl/client on  repl-server-startup [!?] via 🐍 v3.8.13 (pymodbus3.8) +❯ python3 main.py tcp --port 5020 --framer tcp + +---------------------------------------------------------------------------- +__________ _____ .___ __________ .__ +\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | | + | ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| | + | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__ + |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/ + \/ \/ \/ \/ \/ \/|__| + v1.3.1 - 3.6.4 +---------------------------------------------------------------------------- + +> client.read_input_registers address 1 count 1 slave 4 +{ + "registers": [ + 34518 + ] +} + +> client.read_input_registers address 1 count 1 slave 1 +{ + "registers": [ + 32198 + ] +} + +> client.read_input_registers address 1 count 1 slave 2 +{ + "registers": [ + 51557 + ] +} + +> client.read_input_registers address 1 count 1 slave 3 +{ + "original_function_code": "4 (0x4)", + "error": "[Input/Output] Modbus Error: [Invalid Message] No response received, expected at least 8 bytes (0 received)" +} + +``` + + ### Running Tests To run tests, use the following command: diff --git a/poetry.lock b/poetry.lock index 550b6af..e535e49 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -855,4 +855,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "4aa34f98d51f6e3a00c1aaaeffdc3b84e9ba589d234b451ec69d5ff498aabaa2" +content-hash = "b9acf605e21d44fb0dc93d6555cc1eae6fc4e89186c3ae6af83649f77361a936" diff --git a/pymodbus_repl/__init__.py b/pymodbus_repl/__init__.py index 6f16aa2..b2e78ff 100644 --- a/pymodbus_repl/__init__.py +++ b/pymodbus_repl/__init__.py @@ -1 +1,3 @@ """REPL (Read-Eval-Print Loop) tool for working with Modbus devices using the Pymodbus library.""" +__VERSION__ = "2.0.0" + diff --git a/pymodbus_repl/client/main.py b/pymodbus_repl/client/main.py index de61a59..4331277 100644 --- a/pymodbus_repl/client/main.py +++ b/pymodbus_repl/client/main.py @@ -12,6 +12,7 @@ from prompt_toolkit.styles import Style from pygments.lexers.python import PythonLexer from pymodbus import __version__ as pymodbus_version +from pymodbus_repl import __VERSION__ as repl_version from pymodbus.exceptions import ParameterException from pymodbus.transaction import ( ModbusAsciiFramer, @@ -38,7 +39,7 @@ | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__ |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/ \/ \/ \/ \/ \/ \/|__| - v1.3.1 - {pymodbus_version} + v{repl_version} - Pymodbus-{pymodbus_version} ---------------------------------------------------------------------------- """ diff --git a/pymodbus_repl/server/cli.py b/pymodbus_repl/server/cli.py index 5c449a7..5df2986 100644 --- a/pymodbus_repl/server/cli.py +++ b/pymodbus_repl/server/cli.py @@ -8,6 +8,8 @@ from prompt_toolkit.shortcuts import clear from prompt_toolkit.shortcuts.progress_bar import formatters from prompt_toolkit.styles import Style +from pymodbus import __version__ as pymodbus_version +from pymodbus_repl import __VERSION__ as repl_version TITLE = r""" @@ -17,6 +19,7 @@ | | \___ | Y Y ( <_> ) /_/ | | \_\ \ | /\___ \ / \ ___/| | \/\ /\ ___/| | \/ |____| / ____|__|_| /\____/\____ | |___ /____//____ > /_______ /\___ >__| \_/ \___ >__| \/ \/ \/ \/ \/ \/ \/ \/ + v{}-Pymodbus{} """ @@ -111,8 +114,9 @@ def print_title(): max_len = max( # pylint: disable=consider-using-generator [len(t) for t in TITLE.split("\n")] ) + title = TITLE.format(repl_version, pymodbus_version) if col > max_len: - info(TITLE) + info(title) else: print_formatted_text( HTML(f'') diff --git a/pymodbus_repl/server/main.py b/pymodbus_repl/server/main.py index f1cfd13..29ee472 100644 --- a/pymodbus_repl/server/main.py +++ b/pymodbus_repl/server/main.py @@ -1,5 +1,5 @@ """Repl server main.""" -from __future__ import annotations +# from __future__ import annotations import asyncio import contextlib @@ -7,9 +7,11 @@ import logging import sys from enum import Enum -from pathlib import Path +from typing import List, Optional +from pathlib import Path import typer +from typing_extensions import Annotated from pymodbus import pymodbus_apply_logging_config from pymodbus.framer.socket_framer import ModbusSocketFramer from pymodbus.logging import Log @@ -50,7 +52,7 @@ class ModbusFramerTypes(str, Enum): binary = "binary" # pylint: disable=invalid-name -def _completer(incomplete: str, valid_values: list[str]) -> list[str]: +def _completer(incomplete: str, valid_values: List[str]) -> List[str]: """Complete value.""" completion = [] for name in valid_values: @@ -59,19 +61,19 @@ def _completer(incomplete: str, valid_values: list[str]) -> list[str]: return completion -def framers(incomplete: str) -> list[str]: +def framers(incomplete: str) -> List[str]: """Return an autocompleted list of supported clouds.""" _framers = ["socket", "rtu", "tls", "ascii", "binary"] return _completer(incomplete, _framers) -def servers(incomplete: str) -> list[str]: +def servers(incomplete: str) -> List[str]: """Return an autocompleted list of supported clouds.""" _servers = ["tcp", "serial", "tls", "udp"] return _completer(incomplete, _servers) -def process_extra_args(extra_args: list[str], modbus_config: dict) -> dict: +def process_extra_args(extra_args: List[str], modbus_config: dict) -> dict: """Process extra args passed to server.""" options_stripped = [x.strip().replace("--", "") for x in extra_args[::2]] extra_args_dict = dict(list(zip(options_stripped, extra_args[1::2]))) @@ -139,12 +141,12 @@ def run( help="Modbus framer to use", ), modbus_port: str = typer.Option("5020", "--modbus-port", "-p", help="Modbus port"), - modbus_slave_id: list[int] = typer.Option( - [1], "--slave-id", "-u", help="Supported Modbus slave id's" - ), - modbus_config_path: Path = typer.Option( - None, help="Path to additional modbus server config" - ), + modbus_slave_id: Annotated[Optional[List[int]], typer.Option( + "--unit-id", "-u", help="Supported Modbus slave id's" + )] = [1], + modbus_config_path: Annotated[Path, typer.Option( + help="Path to additional modbus server config" + )] = None, randomize: int = typer.Option( 0, "--random", diff --git a/pyproject.toml b/pyproject.toml index 441a774..8a707d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pymodbus_repl" -version = "0.1.0" +version = "2.0.0" description = "REPL (Read-Eval-Print Loop) tool for working with Modbus devices using the Pymodbus library." authors = ["dhoomakethu "] readme = "README.md" @@ -11,7 +11,6 @@ python = "^3.8" typer = {extras = ["all"], version = "^0.9.0"} prompt-toolkit = "^3.0.43" pygments = "^2.17.2" -pytest-xdist = "^3.5.0" [[tool.poetry.dependencies.aiohttp]] python="3.12" @@ -30,6 +29,7 @@ include = ["pymodbus_repl"] pymodbus = "^3.6.4" ruff = "^0.2.1" coverage = "^7.4.1" +pytest-xdist = "^3.5.0" pytest-cov = "^4.1.0" [build-system] @@ -43,7 +43,7 @@ addopts = "--cov-report html" [tool.coverage.run] source = [ - "pymodbus/" + "pymodbus_repl/" ] omit = ["examples/contrib/"] branch = true @@ -56,6 +56,7 @@ exclude_also = [ ] skip_covered = true fail_under = 70.0 +show_missing = true [tool.coverage.html] directory = "build/cov"