Skip to content

Commit

Permalink
Revert to pymodbus 3.1.3
Browse files Browse the repository at this point in the history
It looks like there might be some issues with 3.7.4, see #743.
  • Loading branch information
canton7 committed Jan 31, 2025
1 parent 7c3cbbc commit 1cbf0d3
Show file tree
Hide file tree
Showing 127 changed files with 13,714 additions and 8,545 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.analysis.extraPaths": [
"./custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.7.4"
"./custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.1.3"
]
}
31 changes: 25 additions & 6 deletions custom_components/foxess_modbus/client/custom_modbus_tcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -48,9 +48,13 @@ 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
# In the base method this is 'timeout = self.comm_params.timeout', but that changed from 'self.params.timeout'
# in 3.4.1. So we don't have a consistent way to access the timeout.
# However, this just mirrors what we set, which is the default of 3s. So use that.
# Annoyingly 3.4.1
timeout = 3

# If size isn't specified read up to 4096 bytes at a time.
if size is None:
Expand Down Expand Up @@ -90,5 +94,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
23 changes: 11 additions & 12 deletions custom_components/foxess_modbus/client/modbus_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
from ..const import TCP
from ..const import UDP
from ..inverter_adapters import InverterAdapter
from ..vendor.pymodbus import FramerType
from ..vendor.pymodbus import ModbusPDU
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
Expand All @@ -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,
},
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 11 additions & 8 deletions custom_components/foxess_modbus/vendor/pymodbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@

# 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.7.4").absolute()))
sys.path.insert(0, str((Path(__file__).parent / "pymodbus-3.1.3").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.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 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)

Expand All @@ -25,10 +26,12 @@
"ModbusUdpClient",
"ConnectionException",
"ModbusIOException",
"FramerType",
"ModbusPDU",
"ReadHoldingRegistersResponse",
"ReadInputRegistersResponse",
"WriteMultipleRegistersResponse",
"WriteSingleRegisterResponse",
"ModbusResponse",
"ModbusRtuFramer",
"ModbusSocketFramer",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/local/bin/python3.13
# -*- coding: utf-8 -*-
import re
import sys
from pymodbus.repl.client.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/local/bin/python3.13
# -*- coding: utf-8 -*-
import re
import sys
from pymodbus.repl.server.main import app
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(app())
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Copyright 2008-2023 Pymodbus
Copyright (c) 2011 Galen Collins
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
Expand All @@ -21,3 +22,4 @@ 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.

Loading

0 comments on commit 1cbf0d3

Please sign in to comment.