From 405939edc79c501c7f04f5534b38f4b3db4cbf48 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Fri, 31 Jan 2025 10:42:41 +0000 Subject: [PATCH 1/2] Vendor pymodbus, rather than relying on the version which HA installs This should protect us from HA breaking things in the future! Fixes: #748 --- .pre-commit-config.yaml | 1 + .prettierignore | 1 + .vscode/settings.json | 5 +- .../client/custom_modbus_tcp_client.py | 31 +- .../foxess_modbus/client/modbus_client.py | 35 +- .../flow/adapter_flow_segment.py | 4 +- custom_components/foxess_modbus/manifest.json | 1 - .../foxess_modbus/modbus_controller.py | 2 +- .../services/update_charge_period_service.py | 2 +- .../services/write_registers_service.py | 2 +- .../foxess_modbus/vendor/pymodbus/__init__.py | 37 + .../pymodbus-3.6.9/bin/pymodbus.simulator | 8 + .../pymodbus-3.6.9.dist-info/AUTHORS.rst | 177 ++++ .../pymodbus-3.6.9.dist-info/INSTALLER | 1 + .../pymodbus-3.6.9.dist-info/LICENSE | 23 + .../pymodbus-3.6.9.dist-info/METADATA | 404 ++++++++ .../pymodbus-3.6.9.dist-info/RECORD | 136 +++ .../pymodbus-3.6.9.dist-info/REQUESTED | 0 .../pymodbus-3.6.9.dist-info/WHEEL | 5 + .../pymodbus-3.6.9.dist-info/entry_points.txt | 2 + .../pymodbus-3.6.9.dist-info/top_level.txt | 1 + .../pymodbus-3.6.9.dist-info/zip-safe | 1 + .../pymodbus-3.6.9/pymodbus/__init__.py | 22 + .../pymodbus/bit_read_message.py | 270 ++++++ .../pymodbus/bit_write_message.py | 288 ++++++ .../pymodbus/client/__init__.py | 19 + .../pymodbus-3.6.9/pymodbus/client/base.py | 456 +++++++++ .../pymodbus-3.6.9/pymodbus/client/mixin.py | 560 +++++++++++ .../pymodbus-3.6.9/pymodbus/client/serial.py | 298 ++++++ .../pymodbus-3.6.9/pymodbus/client/tcp.py | 295 ++++++ .../pymodbus-3.6.9/pymodbus/client/tls.py | 240 +++++ .../pymodbus-3.6.9/pymodbus/client/udp.py | 226 +++++ .../pymodbus-3.6.9/pymodbus/constants.py | 144 +++ .../pymodbus/datastore/__init__.py | 21 + .../pymodbus/datastore/context.py | 245 +++++ .../pymodbus/datastore/remote.py | 129 +++ .../pymodbus/datastore/simulator.py | 802 ++++++++++++++++ .../pymodbus/datastore/store.py | 343 +++++++ .../pymodbus-3.6.9/pymodbus/device.py | 591 ++++++++++++ .../pymodbus-3.6.9/pymodbus/diag_message.py | 870 ++++++++++++++++++ .../pymodbus-3.6.9/pymodbus/events.py | 203 ++++ .../pymodbus-3.6.9/pymodbus/exceptions.py | 116 +++ .../pymodbus-3.6.9/pymodbus/factory.py | 291 ++++++ .../pymodbus-3.6.9/pymodbus/file_message.py | 439 +++++++++ .../pymodbus/framer/__init__.py | 40 + .../pymodbus/framer/ascii_framer.py | 82 ++ .../pymodbus-3.6.9/pymodbus/framer/base.py | 150 +++ .../pymodbus/framer/binary_framer.py | 134 +++ .../pymodbus/framer/rtu_framer.py | 237 +++++ .../pymodbus/framer/socket_framer.py | 107 +++ .../pymodbus/framer/tls_framer.py | 80 ++ .../pymodbus-3.6.9/pymodbus/logging.py | 121 +++ .../pymodbus-3.6.9/pymodbus/mei_message.py | 220 +++++ .../pymodbus/message/__init__.py | 7 + .../pymodbus-3.6.9/pymodbus/message/ascii.py | 79 ++ .../pymodbus-3.6.9/pymodbus/message/base.py | 38 + .../pymodbus/message/message.py | 113 +++ .../pymodbus-3.6.9/pymodbus/message/raw.py | 30 + .../pymodbus-3.6.9/pymodbus/message/rtu.py | 219 +++++ .../pymodbus-3.6.9/pymodbus/message/socket.py | 47 + .../pymodbus-3.6.9/pymodbus/message/tls.py | 25 + .../pymodbus-3.6.9/pymodbus/other_message.py | 483 ++++++++++ .../pymodbus-3.6.9/pymodbus/payload.py | 458 +++++++++ .../pymodbus/pymodbus-3.6.9/pymodbus/pdu.py | 260 ++++++ .../pymodbus/pymodbus-3.6.9/pymodbus/py.typed | 0 .../pymodbus/register_read_message.py | 408 ++++++++ .../pymodbus/register_write_message.py | 377 ++++++++ .../pymodbus/server/__init__.py | 42 + .../pymodbus/server/async_io.py | 733 +++++++++++++++ .../pymodbus/server/simulator/__init__.py | 1 + .../server/simulator/custom_actions.py | 10 + .../pymodbus/server/simulator/http_server.py | 668 ++++++++++++++ .../pymodbus/server/simulator/main.py | 128 +++ .../pymodbus/server/simulator/setup.json | 228 +++++ .../server/simulator/web/apple120.png | Bin 0 -> 11369 bytes .../server/simulator/web/apple152.png | Bin 0 -> 15391 bytes .../pymodbus/server/simulator/web/apple60.png | Bin 0 -> 4817 bytes .../pymodbus/server/simulator/web/apple76.png | Bin 0 -> 6344 bytes .../pymodbus/server/simulator/web/favicon.ico | Bin 0 -> 12014 bytes .../server/simulator/web/generator/calls | 127 +++ .../server/simulator/web/generator/log | 39 + .../web/generator/pymodbus_icon_original.png | Bin 0 -> 5850 bytes .../server/simulator/web/generator/registers | 93 ++ .../server/simulator/web/generator/server | 27 + .../pymodbus/server/simulator/web/index.html | 61 ++ .../server/simulator/web/pymodbus.css | 62 ++ .../server/simulator/web/welcome.html | 21 + .../pymodbus-3.6.9/pymodbus/transaction.py | 498 ++++++++++ .../pymodbus/transport/__init__.py | 14 + .../pymodbus/transport/serialtransport.py | 163 ++++ .../pymodbus/transport/transport.py | 654 +++++++++++++ .../pymodbus-3.6.9/pymodbus/utilities.py | 184 ++++ pyproject.toml | 6 + requirements.txt | 2 - 94 files changed, 15188 insertions(+), 35 deletions(-) create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/bin/pymodbus.simulator create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/AUTHORS.rst create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/INSTALLER create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/LICENSE create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/METADATA create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/RECORD create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/REQUESTED create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/WHEEL create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/entry_points.txt create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/top_level.txt create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/zip-safe create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/bit_read_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/bit_write_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/base.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/mixin.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/serial.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/tcp.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/tls.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/client/udp.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/constants.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/datastore/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/datastore/context.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/datastore/remote.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/datastore/simulator.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/datastore/store.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/device.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/diag_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/events.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/exceptions.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/factory.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/file_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/ascii_framer.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/base.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/binary_framer.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/rtu_framer.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/socket_framer.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/framer/tls_framer.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/logging.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/mei_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/ascii.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/base.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/raw.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/rtu.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/socket.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/message/tls.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/other_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/payload.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/pdu.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/py.typed create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/register_read_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/register_write_message.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/async_io.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/custom_actions.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/http_server.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/main.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/setup.json create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/apple120.png create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/apple152.png create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/apple60.png create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/apple76.png create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/favicon.ico create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/generator/calls create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/generator/log create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/generator/pymodbus_icon_original.png create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/generator/registers create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/generator/server create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/index.html create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/pymodbus.css create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/server/simulator/web/welcome.html create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/transaction.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/transport/__init__.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/transport/serialtransport.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/transport/transport.py create mode 100644 custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus/utilities.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7da91946..35b41006 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ repos: - id: check-added-large-files - id: check-yaml - id: end-of-file-fixer + exclude: ^custom_components\/foxess_modbus\/vendor - id: trailing-whitespace - repo: local hooks: diff --git a/.prettierignore b/.prettierignore index bd3739de..c76fb396 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ tests/__snapshots__ +custom_components/foxess_modbus/vendor diff --git a/.vscode/settings.json b/.vscode/settings.json index d9035590..77a53c99 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,5 +27,8 @@ "source.fixAll": "explicit" }, "editor.defaultFormatter": "ms-python.black-formatter" - } + }, + "python.analysis.extraPaths": [ + "./custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9" + ] } diff --git a/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py b/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py index 95f3be77..56635e55 100644 --- a/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py +++ b/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py @@ -5,8 +5,8 @@ from typing import Any from typing import cast -from pymodbus.client import ModbusTcpClient -from pymodbus.exceptions import ConnectionException +from ..vendor.pymodbus import ConnectionException +from ..vendor.pymodbus import ModbusTcpClient _LOGGER = logging.getLogger(__name__) @@ -14,14 +14,14 @@ class CustomModbusTcpClient(ModbusTcpClient): """Custom ModbusTcpClient subclass with some hacks""" - def __init__(self, delay_on_connect: int | None = None, **kwargs: Any) -> None: + def __init__(self, delay_on_connect: int | None, **kwargs: Any) -> None: super().__init__(**kwargs) self._delay_on_connect = delay_on_connect def connect(self) -> bool: was_connected = self.socket is not None if not was_connected: - _LOGGER.debug("Connecting to %s", self.comm_params) + _LOGGER.debug("Connecting to %s", self.params) is_connected = cast(bool, super().connect()) # pymodbus doesn't disable Nagle's algorithm. This slows down reads quite substantially as the # TCP stack waits to see if we're going to send anything else. Disable it ourselves. @@ -34,7 +34,7 @@ def connect(self) -> bool: # Replacement of ModbusTcpClient to use poll rather than select, see # https://github.com/nathanmarlor/foxess_modbus/issues/275 - def recv(self, size: int | None) -> bytes: + def recv(self, size: int) -> bytes: """Read data from the underlying descriptor.""" super(ModbusTcpClient, self).recv(size) if not self.socket: @@ -48,9 +48,9 @@ def recv(self, size: int | None) -> bytes: # is received or timeout is expired. # If timeout expires returns the read data, also if its length is # less than the expected size. - self.socket.setblocking(False) + self.socket.setblocking(0) - timeout = self.comm_params.timeout_connect or 0 + timeout = self.comm_params.timeout_connect # If size isn't specified read up to 4096 bytes at a time. if size is None: @@ -90,5 +90,20 @@ def recv(self, size: int | None) -> bytes: if time_ > end: break - self.last_frame_end = round(time.time(), 6) return b"".join(data) + + # Replacement of ModbusTcpClient to use poll rather than select, see + # https://github.com/nathanmarlor/foxess_modbus/issues/275 + def _check_read_buffer(self) -> bytes | None: + """Check read buffer.""" + time_ = time.time() + end = time_ + self.params.timeout + data = None + + assert self.socket is not None + poll = select.poll() + poll.register(self.socket, select.POLLIN) + poll_res = poll.poll(end - time_) + if len(poll_res) > 0: + data = self.socket.recv(1024) + return data diff --git a/custom_components/foxess_modbus/client/modbus_client.py b/custom_components/foxess_modbus/client/modbus_client.py index 8b6f01ee..dc506e69 100644 --- a/custom_components/foxess_modbus/client/modbus_client.py +++ b/custom_components/foxess_modbus/client/modbus_client.py @@ -11,14 +11,6 @@ import serial from homeassistant.core import HomeAssistant -from pymodbus.client import ModbusSerialClient -from pymodbus.client import ModbusUdpClient -from pymodbus.framer import FramerType -from pymodbus.pdu import ModbusPDU -from pymodbus.pdu.register_read_message import ReadHoldingRegistersResponse -from pymodbus.pdu.register_read_message import ReadInputRegistersResponse -from pymodbus.pdu.register_write_message import WriteMultipleRegistersResponse -from pymodbus.pdu.register_write_message import WriteSingleRegisterResponse from .. import client from ..common.types import ConnectionType @@ -28,6 +20,15 @@ from ..const import TCP from ..const import UDP from ..inverter_adapters import InverterAdapter +from ..vendor.pymodbus import ModbusResponse +from ..vendor.pymodbus import ModbusRtuFramer +from ..vendor.pymodbus import ModbusSerialClient +from ..vendor.pymodbus import ModbusSocketFramer +from ..vendor.pymodbus import ModbusUdpClient +from ..vendor.pymodbus import ReadHoldingRegistersResponse +from ..vendor.pymodbus import ReadInputRegistersResponse +from ..vendor.pymodbus import WriteMultipleRegistersResponse +from ..vendor.pymodbus import WriteSingleRegisterResponse from .custom_modbus_tcp_client import CustomModbusTcpClient _LOGGER = logging.getLogger(__name__) @@ -38,19 +39,19 @@ _CLIENTS: dict[str, dict[str, Any]] = { SERIAL: { "client": ModbusSerialClient, - "framer": FramerType.RTU, + "framer": ModbusRtuFramer, }, TCP: { "client": CustomModbusTcpClient, - "framer": FramerType.SOCKET, + "framer": ModbusSocketFramer, }, UDP: { "client": ModbusUdpClient, - "framer": FramerType.SOCKET, + "framer": ModbusSocketFramer, }, RTU_OVER_TCP: { "client": CustomModbusTcpClient, - "framer": FramerType.RTU, + "framer": ModbusRtuFramer, }, } @@ -69,16 +70,14 @@ def __init__(self, hass: HomeAssistant, protocol: str, adapter: InverterAdapter, client = _CLIENTS[protocol] + # Delaying for a second after establishing a connection seems to help the inverter stability, + # see https://github.com/nathanmarlor/foxess_modbus/discussions/132 config = { **config, "framer": client["framer"], + "delay_on_connect": 1 if adapter.connection_type == ConnectionType.LAN else None, } - # Delaying for a second after establishing a connection seems to help the inverter stability, - # see https://github.com/nathanmarlor/foxess_modbus/discussions/132 - if adapter.connection_type == ConnectionType.LAN: - config["delay_on_connect"] = 1 - # If our custom PosixPollSerial hack is supported, use that. This uses poll rather than select, which means we # don't break when there are more than 1024 fds. See #457. # Only supported on posix, see https://github.com/pyserial/pyserial/blob/7aeea35429d15f3eefed10bbb659674638903e3a/serial/__init__.py#L31 @@ -225,7 +224,7 @@ def __str__(self) -> str: class ModbusClientFailedError(Exception): """Raised when the ModbusClient fails to read/write""" - def __init__(self, message: str, client: ModbusClient, response: ModbusPDU | Exception) -> None: + def __init__(self, message: str, client: ModbusClient, response: ModbusResponse | Exception) -> None: super().__init__(f"{message} from {client}: {response}") self.message = message self.client = client diff --git a/custom_components/foxess_modbus/flow/adapter_flow_segment.py b/custom_components/foxess_modbus/flow/adapter_flow_segment.py index f038b7a2..523793b1 100644 --- a/custom_components/foxess_modbus/flow/adapter_flow_segment.py +++ b/custom_components/foxess_modbus/flow/adapter_flow_segment.py @@ -7,8 +7,6 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import selector -from pymodbus.exceptions import ConnectionException -from pymodbus.exceptions import ModbusIOException from ..client.modbus_client import ModbusClient from ..client.modbus_client import ModbusClientFailedError @@ -23,6 +21,8 @@ from ..inverter_adapters import InverterAdapter from ..inverter_adapters import InverterAdapterType from ..modbus_controller import ModbusController +from ..vendor.pymodbus import ConnectionException +from ..vendor.pymodbus import ModbusIOException from .flow_handler_mixin import FlowHandlerMixin from .flow_handler_mixin import ValidationFailedError from .inverter_data import InverterData diff --git a/custom_components/foxess_modbus/manifest.json b/custom_components/foxess_modbus/manifest.json index 9d774a57..dcbc111e 100755 --- a/custom_components/foxess_modbus/manifest.json +++ b/custom_components/foxess_modbus/manifest.json @@ -8,6 +8,5 @@ "integration_type": "service", "iot_class": "local_push", "issue_tracker": "https://github.com/nathanmarlor/foxess_modbus/issues", - "requirements": ["pymodbus>=3.7.4"], "version": "1.0.0" } diff --git a/custom_components/foxess_modbus/modbus_controller.py b/custom_components/foxess_modbus/modbus_controller.py index abceb8a2..5d29381e 100644 --- a/custom_components/foxess_modbus/modbus_controller.py +++ b/custom_components/foxess_modbus/modbus_controller.py @@ -18,7 +18,6 @@ from homeassistant.helpers import issue_registry from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.issue_registry import IssueSeverity -from pymodbus.exceptions import ConnectionException from .client.modbus_client import ModbusClient from .client.modbus_client import ModbusClientFailedError @@ -38,6 +37,7 @@ from .inverter_profiles import INVERTER_PROFILES from .inverter_profiles import InverterModelConnectionTypeProfile from .remote_control_manager import RemoteControlManager +from .vendor.pymodbus import ConnectionException _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/foxess_modbus/services/update_charge_period_service.py b/custom_components/foxess_modbus/services/update_charge_period_service.py index 9f28d161..e3d967bb 100644 --- a/custom_components/foxess_modbus/services/update_charge_period_service.py +++ b/custom_components/foxess_modbus/services/update_charge_period_service.py @@ -10,13 +10,13 @@ from homeassistant.core import ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from pymodbus.exceptions import ModbusIOException from ..const import DOMAIN from ..entities.modbus_charge_period_sensors import is_time_value_valid from ..entities.modbus_charge_period_sensors import parse_time_value from ..entities.modbus_charge_period_sensors import serialize_time_to_value from ..modbus_controller import ModbusController +from ..vendor.pymodbus import ModbusIOException from .utils import get_controller_from_friendly_name_or_device_id _LOGGER: logging.Logger = logging.getLogger(__package__) diff --git a/custom_components/foxess_modbus/services/write_registers_service.py b/custom_components/foxess_modbus/services/write_registers_service.py index 3b3fa659..c42b821a 100644 --- a/custom_components/foxess_modbus/services/write_registers_service.py +++ b/custom_components/foxess_modbus/services/write_registers_service.py @@ -8,10 +8,10 @@ from homeassistant.core import ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from pymodbus.exceptions import ModbusIOException from ..const import DOMAIN from ..modbus_controller import ModbusController +from ..vendor.pymodbus import ModbusIOException from .utils import get_controller_from_friendly_name_or_device_id _LOGGER: logging.Logger = logging.getLogger(__package__) diff --git a/custom_components/foxess_modbus/vendor/pymodbus/__init__.py b/custom_components/foxess_modbus/vendor/pymodbus/__init__.py new file mode 100644 index 00000000..8ca5ec6a --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/__init__.py @@ -0,0 +1,37 @@ +import sys +from pathlib import Path + +# Update python.analysis.extraPaths in .vscode/settings.json if you change this. +# If changed, make sure subclasses in modbus_client are still valid! +sys.path.insert(0, str((Path(__file__).parent / "pymodbus-3.6.9").absolute())) + +from pymodbus.client import ModbusSerialClient +from pymodbus.client import ModbusTcpClient +from pymodbus.client import ModbusUdpClient +from pymodbus.exceptions import ConnectionException +from pymodbus.exceptions import ModbusIOException +from pymodbus.register_read_message import ReadHoldingRegistersResponse +from pymodbus.register_read_message import ReadInputRegistersResponse +from pymodbus.register_write_message import WriteMultipleRegistersResponse +from pymodbus.register_write_message import WriteSingleRegisterResponse +from pymodbus.pdu import ModbusResponse +from pymodbus.transaction import ModbusRtuFramer +from pymodbus.transaction import ModbusSocketFramer + +sys.path.pop(0) + +__all__ = [ + "ModbusSerialClient", + "ModbusTcpClient", + "ModbusUdpClient", + "ConnectionException", + "ModbusIOException", + "ModbusPDU", + "ReadHoldingRegistersResponse", + "ReadInputRegistersResponse", + "WriteMultipleRegistersResponse", + "WriteSingleRegisterResponse", + "ModbusResponse", + "ModbusRtuFramer", + "ModbusSocketFramer", +] diff --git a/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/bin/pymodbus.simulator b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/bin/pymodbus.simulator new file mode 100644 index 00000000..c2716c0b --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/bin/pymodbus.simulator @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.13 +# -*- coding: utf-8 -*- +import re +import sys +from pymodbus.server.simulator.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/AUTHORS.rst b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/AUTHORS.rst new file mode 100644 index 00000000..6d015f1e --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/AUTHORS.rst @@ -0,0 +1,177 @@ +Authors +======= +All these versions would not be possible without volunteers! + +This is a complete list for each major version. + +A big "thank you" to everybody who helped out. + +Pymodbus version 3 family +------------------------- +Thanks to + +- AKJ7 +- Alex +- Alex Ruddick +- Alexander Lanin +- Alexandre CUER +- Alois Hockenschlohe +- Arjan +- André Srinivasan +- andrew-harness +- banana-sun +- Blaise Thompson +- CapraTheBest +- cgernert +- corollaries +- Chandler Riehm +- Chris Hung +- Christian Krause +- dhoomakethu +- doelki +- DominicDataP +- Dominique Martinet +- Dries +- duc996 +- Farzad Panahi +- Fredo70 +- Gao Fang +- Ghostkeeper +- Hangyu Fan +- Hayden Roche +- Iktek +- Ilkka Ollakka +- Jakob Ruhe +- Jakob Schlyter +- James Braza +- James Cameron +- James Hilliard +- jan iversen +- Jerome Velociter +- Joe Burmeister +- John Miko +- Jonathan Reichelt Gjertsen +- julian +- Justin Standring +- Kenny Johansson +- Matthias Straka +- laund +- Logan Gunthorpe +- Marko Luther +- Logan Gunthorpe +- Marko Luther +- Matthias Straka +- Mickaël Schoentgen +- Pavel Kostromitinov +- peufeu2 +- Philip Couling +- Qi Li +- Sebastian Machuca +- Sefa Keleş +- Steffen Beyer +- sumguytho +- Thijs W +- Totally a booplicate +- WouterTuinstra +- wriswith +- Yohrog +- yyokusa + + +Pymodbus version 2 family +------------------------- +Thanks to + +- alecjohanson +- Alexey Andreyev +- Andrea Canidio +- Carlos Gomez +- Cougar +- Christian Sandberg +- dhoomakethu +- dices +- Dmitri Zimine +- Emil Vanherp +- er888kh +- Eric Duminil +- Erlend Egeberg Aasland +- hackerboygn +- Jian-Hong Pan +- Jose J Rodriguez +- Justin Searle +- Karl Palsson +- Kim Hansen +- Kristoffer Sjöberg +- Kyle Altendorf +- Lars Kruse +- Malte Kliemann +- Memet Bilgin +- Michael Corcoran +- Mike +- sanjay +- Sekenre +- Siarhei Farbotka +- Steffen Vogel +- tcplomp +- Thor Michael Støre +- Tim Gates +- Ville Skyttä +- Wild Stray +- Yegor Yefremov + + +Pymodbus version 1 family +------------------------- +Thanks to + +- Antoine Pitrou +- Bart de Waal +- bashwork +- bje- +- Claudio Catterina +- Chintalagiri Shashank +- dhoomakethu +- dragoshenron +- Elvis Stansvik +- Eren Inan Canpolat +- Everley +- Fabio Bonelli +- fleimgruber +- francozappa +- Galen Collins +- Gordon Broom +- Hamilton Kibbe +- Hynek Petrak +- idahogray +- Ingo van Lil +- Jack +- jbiswas +- jon mills +- Josh Kelley +- Karl Palsson +- Matheus Frata +- Patrick Fuller +- Perry Kundert +- Philippe Gauthier +- Rahul Raghunath +- sanjay +- schubduese42 +- semyont +- Semyon Teplitsky +- Stuart Longland +- Yegor Yefremov + + +Pymodbus version 0 family +------------------------- +Thanks to + +- Albert Brandl +- Galen Collins + +Import to github was based on code from: + +- S.W.A.C. GmbH, Germany. +- S.W.A.C. Bohemia s.r.o., Czech Republic. +- Hynek Petrak +- Galen Collins diff --git a/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/INSTALLER b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/LICENSE b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/LICENSE new file mode 100644 index 00000000..d3dda3d9 --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/LICENSE @@ -0,0 +1,23 @@ +Copyright 2008-2023 Pymodbus + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/METADATA b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/METADATA new file mode 100644 index 00000000..9642d6e2 --- /dev/null +++ b/custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.6.9/pymodbus-3.6.9.dist-info/METADATA @@ -0,0 +1,404 @@ +Metadata-Version: 2.1 +Name: pymodbus +Version: 3.6.9 +Summary: A fully featured modbus protocol stack in python +Author: Galen Collins, Jan Iversen +Maintainer: dhoomakethu, janiversen +License: BSD-3-Clause +Project-URL: Homepage, https://github.com/pymodbus-dev/pymodbus/ +Project-URL: Source Code, https://github.com/pymodbus-dev/pymodbus +Project-URL: Bug Reports, https://github.com/pymodbus-dev/pymodbus/issues +Project-URL: Docs: Dev, https://pymodbus.readthedocs.io/en/latest/?badge=latest +Project-URL: Discord, https://discord.gg/vcP8qAz2 +Keywords: modbus,asyncio,scada,client,server,simulator +Platform: 'Linux' +Platform: 'Mac OS X' +Platform: 'Win' +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Framework :: AsyncIO +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: OS Independent +Classifier: Operating System :: Microsoft +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: System :: Networking +Classifier: Topic :: Utilities +Requires-Python: >=3.8.0 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: AUTHORS.rst +Provides-Extra: all +Requires-Dist: pymodbus[development,documentation,repl,serial,simulator] ; extra == 'all' +Provides-Extra: development +Requires-Dist: build >=1.1.1 ; extra == 'development' +Requires-Dist: codespell >=2.2.6 ; extra == 'development' +Requires-Dist: coverage >=7.4.3 ; extra == 'development' +Requires-Dist: mypy >=1.9.0 ; extra == 'development' +Requires-Dist: pylint >=3.1.0 ; extra == 'development' +Requires-Dist: pytest >=8.1.0 ; extra == 'development' +Requires-Dist: pytest-asyncio >=0.23.5.post1 ; extra == 'development' +Requires-Dist: pytest-cov >=4.1.0 ; extra == 'development' +Requires-Dist: pytest-profiling >=1.7.0 ; extra == 'development' +Requires-Dist: pytest-timeout >=2.3.1 ; extra == 'development' +Requires-Dist: pytest-xdist >=3.5.0 ; extra == 'development' +Requires-Dist: ruff >=0.3.3 ; extra == 'development' +Requires-Dist: twine >=5.0.0 ; extra == 'development' +Requires-Dist: types-Pygments ; extra == 'development' +Requires-Dist: types-pyserial ; extra == 'development' +Provides-Extra: documentation +Requires-Dist: recommonmark >=0.7.1 ; extra == 'documentation' +Requires-Dist: Sphinx >=5.3.0 ; extra == 'documentation' +Requires-Dist: sphinx-rtd-theme >=1.1.1 ; extra == 'documentation' +Provides-Extra: repl +Requires-Dist: pymodbus-repl >=2.0.3 ; extra == 'repl' +Provides-Extra: serial +Requires-Dist: pyserial >=3.5 ; extra == 'serial' +Provides-Extra: simulator +Requires-Dist: aiohttp >=3.8.6 ; (python_version < "3.12") and extra == 'simulator' +Requires-Dist: aiohttp >=3.9.0b0 ; (python_version == "3.12") and extra == 'simulator' + +PyModbus - A Python Modbus Stack +================================ +.. image:: https://github.com/pymodbus-dev/pymodbus/actions/workflows/ci.yml/badge.svg?branch=dev + :target: https://github.com/pymodbus-dev/pymodbus/actions/workflows/ci.yml +.. image:: https://readthedocs.org/projects/pymodbus/badge/?version=latest + :target: https://pymodbus.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status +.. image:: https://pepy.tech/badge/pymodbus + :target: https://pepy.tech/project/pymodbus + :alt: Downloads + +Pymodbus is a full Modbus protocol implementation offering client/server with synchronous/asynchronous API a well as simulators. + +Current release is `3.6.9 `_. + +Bleeding edge (not released) is `dev `_. + +All changes are described in `release notes `_ +and all API changes are `documented `_ + +A big thanks to all the `volunteers `_ that helps make pymodbus a great project. + +Source code on `github `_ + +Pymodbus in a nutshell +---------------------- +Pymodbus consist of 5 parts: + +- **client**, connect to your favorite device(s) +- **server**, simulate your favorite device(s) +- **repl**, a commandline text based client/server simulator +- **simulator**, an html based server simulator +- **examples**, showing both simple and advances usage + +Common features +^^^^^^^^^^^^^^^ +* Full modbus standard protocol implementation +* Support for custom function codes +* support serial (rs-485), tcp, tls and udp communication +* support all standard frames: socket, rtu, rtu-over-tcp, tcp and ascii +* does not have third party dependencies, apart from pyserial (optional) +* very lightweight project +* requires Python >= 3.8 +* thorough test suite, that test all corners of the library +* automatically tested on Windows, Linux and MacOS combined with python 3.8 - 3.12 +* strongly typed API (py.typed present) + +The modbus protocol specification: Modbus_Application_Protocol_V1_1b3.pdf can be found on +`modbus org `_ + + +Client Features +^^^^^^^^^^^^^^^ +* asynchronous API and synchronous API for applications +* very simple setup and call sequence (just 6 lines of code) +* utilities to convert int/float to/from multiple registers +* payload builder/decoder to help with complex data + +`Client documentation `_ + + +Server Features +^^^^^^^^^^^^^^^ +* asynchronous implementation for high performance +* synchronous API classes for convenience +* simulate real life devices +* full server control context (device information, counters, etc) +* different backend datastores to manage register values +* callback to intercept requests/responses +* work on RS485 in parallel with other devices + +`Server documentation `_ + + +REPL Features +^^^^^^^^^^^^^ +- server/client commandline emulator +- easy test of real device (client) +- easy test of client app (server) +- simulation of broken requests/responses +- simulation of error responses (hard to provoke in real devices) + +`REPL documentation `_ + + +Simulator Features +^^^^^^^^^^^^^^^^^^ +- server simulator with WEB interface +- configure the structure of a real device +- monitor traffic online +- allow distributed team members to work on a virtual device using internet +- simulation of broken requests/responses +- simulation of error responses (hard to provoke in real devices) + +`Simulator documentation `_ + +Use Cases +--------- +The client is the most typically used. It is embedded into applications, +where it abstract the modbus protocol from the application by providing an +easy to use API. The client is integrated into some well known projects like +`home-assistant `_. + +Although most system administrators will find little need for a Modbus +server, the server is handy to verify the functionality of an application. + +The simulator and/or server is often used to simulate real life devices testing +applications. The server is excellent to perform high volume testing (e.g. +houndreds of devices connected to the application). The advantage of the server is +that it runs not only a "normal" computers but also on small ones like Raspberry PI. + +Since the library is written in python, it allows for easy scripting and/or integration into their existing +solutions. + +For more information please browse the project documentation: + +https://readthedocs.org/docs/pymodbus/en/latest/index.html + + + +Install +------- +The library is available on pypi.org and github.com to install with + +- :code:`pip` for those who just want to use the library +- :code:`git clone` for those who wants to help or just are curious + +Be aware that there are a number of project, who have forked pymodbus and + +- seems just to provide a version frozen in time +- extended pymodbus with extra functionality + +The latter is not because we rejected the extra functionality (we welcome all changes), +but because the codeowners made that decision. + +In both cases, please understand, we cannot offer support to users of these projects as we do not known +what have been changed nor what status the forked code have. + +A growing number of Linux distributions include pymodbus in their standard installation. + +You need to have python3 installed, preferable 3.11. + +Install with pip +^^^^^^^^^^^^^^^^ +You can install using pip by issuing the following +commands in a terminal window:: + + pip install pymodbus + +If you want to use the serial interface:: + + pip install pymodbus[serial] + +This will install pymodbus with the pyserial dependency. + +Pymodbus offers a number of extra options: + +- **repl**, needed by pymodbus.repl +- **serial**, needed for serial communication +- **simulator**, needed by pymodbus.simulator +- **documentation**, needed to generate documentation +- **development**, needed for development +- **all**, installs all of the above + +which can be installed as:: + + pip install pymodbus[