Skip to content

Commit 1cbf0d3

Browse files
committed
Revert to pymodbus 3.1.3
It looks like there might be some issues with 3.7.4, see #743.
1 parent 7c3cbbc commit 1cbf0d3

File tree

127 files changed

+13714
-8545
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+13714
-8545
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@
2929
"editor.defaultFormatter": "ms-python.black-formatter"
3030
},
3131
"python.analysis.extraPaths": [
32-
"./custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.7.4"
32+
"./custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.1.3"
3333
]
3434
}

custom_components/foxess_modbus/client/custom_modbus_tcp_client.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
class CustomModbusTcpClient(ModbusTcpClient):
1515
"""Custom ModbusTcpClient subclass with some hacks"""
1616

17-
def __init__(self, delay_on_connect: int | None = None, **kwargs: Any) -> None:
17+
def __init__(self, delay_on_connect: int | None, **kwargs: Any) -> None:
1818
super().__init__(**kwargs)
1919
self._delay_on_connect = delay_on_connect
2020

2121
def connect(self) -> bool:
2222
was_connected = self.socket is not None
2323
if not was_connected:
24-
_LOGGER.debug("Connecting to %s", self.comm_params)
24+
_LOGGER.debug("Connecting to %s", self.params)
2525
is_connected = cast(bool, super().connect())
2626
# pymodbus doesn't disable Nagle's algorithm. This slows down reads quite substantially as the
2727
# TCP stack waits to see if we're going to send anything else. Disable it ourselves.
@@ -34,7 +34,7 @@ def connect(self) -> bool:
3434

3535
# Replacement of ModbusTcpClient to use poll rather than select, see
3636
# https://github.com/nathanmarlor/foxess_modbus/issues/275
37-
def recv(self, size: int | None) -> bytes:
37+
def recv(self, size: int) -> bytes:
3838
"""Read data from the underlying descriptor."""
3939
super(ModbusTcpClient, self).recv(size)
4040
if not self.socket:
@@ -48,9 +48,13 @@ def recv(self, size: int | None) -> bytes:
4848
# is received or timeout is expired.
4949
# If timeout expires returns the read data, also if its length is
5050
# less than the expected size.
51-
self.socket.setblocking(False)
51+
self.socket.setblocking(0)
5252

53-
timeout = self.comm_params.timeout_connect or 0
53+
# In the base method this is 'timeout = self.comm_params.timeout', but that changed from 'self.params.timeout'
54+
# in 3.4.1. So we don't have a consistent way to access the timeout.
55+
# However, this just mirrors what we set, which is the default of 3s. So use that.
56+
# Annoyingly 3.4.1
57+
timeout = 3
5458

5559
# If size isn't specified read up to 4096 bytes at a time.
5660
if size is None:
@@ -90,5 +94,20 @@ def recv(self, size: int | None) -> bytes:
9094
if time_ > end:
9195
break
9296

93-
self.last_frame_end = round(time.time(), 6)
9497
return b"".join(data)
98+
99+
# Replacement of ModbusTcpClient to use poll rather than select, see
100+
# https://github.com/nathanmarlor/foxess_modbus/issues/275
101+
def _check_read_buffer(self) -> bytes | None:
102+
"""Check read buffer."""
103+
time_ = time.time()
104+
end = time_ + self.params.timeout
105+
data = None
106+
107+
assert self.socket is not None
108+
poll = select.poll()
109+
poll.register(self.socket, select.POLLIN)
110+
poll_res = poll.poll(end - time_)
111+
if len(poll_res) > 0:
112+
data = self.socket.recv(1024)
113+
return data

custom_components/foxess_modbus/client/modbus_client.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
from ..const import TCP
2121
from ..const import UDP
2222
from ..inverter_adapters import InverterAdapter
23-
from ..vendor.pymodbus import FramerType
24-
from ..vendor.pymodbus import ModbusPDU
23+
from ..vendor.pymodbus import ModbusResponse
24+
from ..vendor.pymodbus import ModbusRtuFramer
2525
from ..vendor.pymodbus import ModbusSerialClient
26+
from ..vendor.pymodbus import ModbusSocketFramer
2627
from ..vendor.pymodbus import ModbusUdpClient
2728
from ..vendor.pymodbus import ReadHoldingRegistersResponse
2829
from ..vendor.pymodbus import ReadInputRegistersResponse
@@ -38,19 +39,19 @@
3839
_CLIENTS: dict[str, dict[str, Any]] = {
3940
SERIAL: {
4041
"client": ModbusSerialClient,
41-
"framer": FramerType.RTU,
42+
"framer": ModbusRtuFramer,
4243
},
4344
TCP: {
4445
"client": CustomModbusTcpClient,
45-
"framer": FramerType.SOCKET,
46+
"framer": ModbusSocketFramer,
4647
},
4748
UDP: {
4849
"client": ModbusUdpClient,
49-
"framer": FramerType.SOCKET,
50+
"framer": ModbusSocketFramer,
5051
},
5152
RTU_OVER_TCP: {
5253
"client": CustomModbusTcpClient,
53-
"framer": FramerType.RTU,
54+
"framer": ModbusRtuFramer,
5455
},
5556
}
5657

@@ -69,16 +70,14 @@ def __init__(self, hass: HomeAssistant, protocol: str, adapter: InverterAdapter,
6970

7071
client = _CLIENTS[protocol]
7172

73+
# Delaying for a second after establishing a connection seems to help the inverter stability,
74+
# see https://github.com/nathanmarlor/foxess_modbus/discussions/132
7275
config = {
7376
**config,
7477
"framer": client["framer"],
78+
"delay_on_connect": 1 if adapter.connection_type == ConnectionType.LAN else None,
7579
}
7680

77-
# Delaying for a second after establishing a connection seems to help the inverter stability,
78-
# see https://github.com/nathanmarlor/foxess_modbus/discussions/132
79-
if adapter.connection_type == ConnectionType.LAN:
80-
config["delay_on_connect"] = 1
81-
8281
# If our custom PosixPollSerial hack is supported, use that. This uses poll rather than select, which means we
8382
# don't break when there are more than 1024 fds. See #457.
8483
# Only supported on posix, see https://github.com/pyserial/pyserial/blob/7aeea35429d15f3eefed10bbb659674638903e3a/serial/__init__.py#L31
@@ -225,7 +224,7 @@ def __str__(self) -> str:
225224
class ModbusClientFailedError(Exception):
226225
"""Raised when the ModbusClient fails to read/write"""
227226

228-
def __init__(self, message: str, client: ModbusClient, response: ModbusPDU | Exception) -> None:
227+
def __init__(self, message: str, client: ModbusClient, response: ModbusResponse | Exception) -> None:
229228
super().__init__(f"{message} from {client}: {response}")
230229
self.message = message
231230
self.client = client

custom_components/foxess_modbus/vendor/pymodbus/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33

44
# Update python.analysis.extraPaths in .vscode/settings.json if you change this.
55
# If changed, make sure subclasses in modbus_client are still valid!
6-
sys.path.insert(0, str((Path(__file__).parent / "pymodbus-3.7.4").absolute()))
6+
sys.path.insert(0, str((Path(__file__).parent / "pymodbus-3.1.3").absolute()))
77

88
from pymodbus.client import ModbusSerialClient
99
from pymodbus.client import ModbusTcpClient
1010
from pymodbus.client import ModbusUdpClient
1111
from pymodbus.exceptions import ConnectionException
1212
from pymodbus.exceptions import ModbusIOException
13-
from pymodbus.framer import FramerType
14-
from pymodbus.pdu import ModbusPDU
15-
from pymodbus.pdu.register_read_message import ReadHoldingRegistersResponse
16-
from pymodbus.pdu.register_read_message import ReadInputRegistersResponse
17-
from pymodbus.pdu.register_write_message import WriteMultipleRegistersResponse
18-
from pymodbus.pdu.register_write_message import WriteSingleRegisterResponse
13+
from pymodbus.register_read_message import ReadHoldingRegistersResponse
14+
from pymodbus.register_read_message import ReadInputRegistersResponse
15+
from pymodbus.register_write_message import WriteMultipleRegistersResponse
16+
from pymodbus.register_write_message import WriteSingleRegisterResponse
17+
from pymodbus.pdu import ModbusResponse
18+
from pymodbus.transaction import ModbusRtuFramer
19+
from pymodbus.transaction import ModbusSocketFramer
1920

2021
sys.path.pop(0)
2122

@@ -25,10 +26,12 @@
2526
"ModbusUdpClient",
2627
"ConnectionException",
2728
"ModbusIOException",
28-
"FramerType",
2929
"ModbusPDU",
3030
"ReadHoldingRegistersResponse",
3131
"ReadInputRegistersResponse",
3232
"WriteMultipleRegistersResponse",
3333
"WriteSingleRegisterResponse",
34+
"ModbusResponse",
35+
"ModbusRtuFramer",
36+
"ModbusSocketFramer",
3437
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/local/bin/python3.13
2+
# -*- coding: utf-8 -*-
3+
import re
4+
import sys
5+
from pymodbus.repl.client.main import main
6+
if __name__ == '__main__':
7+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8+
sys.exit(main())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/local/bin/python3.13
2+
# -*- coding: utf-8 -*-
3+
import re
4+
import sys
5+
from pymodbus.repl.server.main import app
6+
if __name__ == '__main__':
7+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8+
sys.exit(app())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/local/bin/python3.13
2+
# -*- coding: utf-8 -*-
3+
import re
4+
import sys
5+
from pymodbus.server.simulator.main import main
6+
if __name__ == '__main__':
7+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8+
sys.exit(main())

custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.7.4/pymodbus-3.7.4.dist-info/LICENSE renamed to custom_components/foxess_modbus/vendor/pymodbus/pymodbus-3.1.3/pymodbus-3.1.3.dist-info/LICENSE

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Copyright 2008-2023 Pymodbus
1+
Copyright (c) 2011 Galen Collins
2+
All rights reserved.
23

34
Redistribution and use in source and binary forms, with or without
45
modification, are permitted provided that the following conditions
@@ -21,3 +22,4 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2122
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2223
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2324
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+

0 commit comments

Comments
 (0)