Bump modbus-connection to 4.12.0 (#182149)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-09-14 06:50:17 +02:00
committed by GitHub
co-authored by Claude
parent 09e3474f91
commit 48671e144c
6 changed files with 88 additions and 13 deletions
@@ -34,6 +34,27 @@ DATA_MODBUS_CONNECTIONS: HassKey[dict[ModbusEndpoint, _SharedConnection]] = Hass
)
def _canonical(params: ModbusParams) -> ModbusParams:
"""Return the params in the form the link is actually built from.
RTU and ASCII frame a serial line, so `ModbusTcpParams` carrying one builds
the serial link over a `socket://` device that `ModbusSerialParams` names
directly. Both spellings of that link have to reach the same connection.
"""
if not isinstance(params, ModbusTcpParams) or params.framer not in ("rtu", "ascii"):
return params
# An IPv6 literal is bracketed, or its own colons read as the port separator.
host = f"[{params.host}]" if ":" in params.host else params.host
return ModbusSerialParams(
device=f"socket://{host}:{params.port}",
framer=params.framer,
# The line speed modbus-connection gives a socket-carried serial framing.
# It is fast enough to land on tmodbus's 1.75 ms inter-frame gap floor,
# which any value at or above 19200 baud also gets.
baudrate=115200,
)
@dataclass
class _SharedConnection:
"""A connection and the units held on it."""
@@ -77,6 +98,7 @@ def _async_acquire(
Raises `HomeAssistantError` if the device is already in use over different
link settings, which cannot both be honoured on one connection.
"""
params = _canonical(params)
endpoint = params.endpoint
connections = hass.data.setdefault(DATA_MODBUS_CONNECTIONS, {})
if (shared := connections.get(endpoint)) is None:
@@ -7,7 +7,7 @@
"loggers": ["pymodbus"],
"requirements": [
"pymodbus==3.13.1",
"modbus-connection[tmodbus]==4.11.1",
"modbus-connection[tmodbus]==4.12.0",
"tmodbus==0.6.2"
]
}
+1 -1
View File
@@ -1641,7 +1641,7 @@ mitsubishi-comfort==0.5.2
moat-ble==0.1.1
# homeassistant.components.modbus
modbus-connection[tmodbus]==4.11.1
modbus-connection[tmodbus]==4.12.0
# homeassistant.components.moehlenhoff_alpha2
moehlenhoff-alpha2==1.4.0
+60 -5
View File
@@ -3,7 +3,7 @@
from collections.abc import Callable, Generator
from unittest.mock import AsyncMock, patch
from modbus_connection import ModbusSerialParams, ModbusTcpParams
from modbus_connection import ModbusSerialParams, ModbusTcpParams, ModbusTlsParams
from modbus_connection.tmodbus import ModbusConnection
import pytest
@@ -100,6 +100,63 @@ async def test_the_same_device_reached_by_a_different_name_still_shares(
assert len(hass.data[DATA_MODBUS_CONNECTIONS]) == 1
@pytest.mark.parametrize(
"device",
[
pytest.param("socket://1.2.3.4:502", id="ipv4"),
pytest.param("socket://[fe80::1]:502", id="ipv6"),
],
)
async def test_a_serial_framing_over_a_socket_shares_the_serial_link(
hass: HomeAssistant, consumer: ConsumerFactory, device: str
) -> None:
"""A serial framing on a TCP link is the serial link the socket device names.
Both spellings build one link at one line speed, so splitting them would put
a second connection on a gateway that can only answer one.
"""
entry = consumer()
await hass.config_entries.async_setup(entry.entry_id)
host = device.removeprefix("socket://").rpartition(":")[0].strip("[]")
async_get_unit(
hass, entry, ModbusSerialParams(device=device, framer="rtu", baudrate=115200), 1
)
with pytest.deprecated_call():
async_get_unit(
hass, entry, ModbusTcpParams(host=host, port=502, framer="rtu"), 2
)
assert len(hass.data[DATA_MODBUS_CONNECTIONS]) == 1
async def test_a_serial_framing_over_a_socket_keeps_the_line_speed_asked_for(
hass: HomeAssistant, consumer: ConsumerFactory
) -> None:
"""The framing alone says nothing about the line speed, so it cannot assume one.
A slower line needs a longer gap between frames, which the connection built
for the faster one does not leave.
"""
entry = consumer()
await hass.config_entries.async_setup(entry.entry_id)
async_get_unit(
hass,
entry,
ModbusSerialParams(device="socket://1.2.3.4:502", framer="rtu", baudrate=9600),
1,
)
with (
pytest.deprecated_call(),
pytest.raises(HomeAssistantError, match="different link settings"),
):
async_get_unit(
hass, entry, ModbusTcpParams(host="1.2.3.4", port=502, framer="rtu"), 2
)
async def test_one_device_cannot_be_used_with_two_link_settings(
hass: HomeAssistant, consumer: ConsumerFactory
) -> None:
@@ -114,9 +171,7 @@ async def test_one_device_cannot_be_used_with_two_link_settings(
async_get_unit(hass, entry, ModbusTcpParams(host="1.2.3.4", port=502), 1)
with pytest.raises(HomeAssistantError, match="different link settings"):
async_get_unit(
hass, entry, ModbusTcpParams(host="1.2.3.4", port=502, framer="rtu"), 2
)
async_get_unit(hass, entry, ModbusTlsParams(host="1.2.3.4", port=502), 2)
async def test_the_last_consumer_closes_the_connection(
@@ -251,7 +306,7 @@ async def test_a_temporary_unit_cannot_clash_with_held_link_settings(
with pytest.raises(HomeAssistantError, match="different link settings"):
async with async_get_temporary_unit(
hass, ModbusTcpParams(host="1.2.3.4", port=502, framer="rtu"), 2
hass, ModbusTlsParams(host="1.2.3.4", port=502), 2
):
pass
@@ -2,7 +2,7 @@
from unittest.mock import MagicMock
from modbus_connection import ModbusTcpParams
from modbus_connection import ModbusTlsParams
from pystiebeleltron import ControllerModel, StiebelEltronModbusError
import pytest
@@ -79,9 +79,7 @@ async def test_form_conflicting_link_settings(hass: HomeAssistant) -> None:
async_get_unit(
hass,
other_entry,
ModbusTcpParams(
host=USER_INPUT[CONF_HOST], port=USER_INPUT[CONF_PORT], framer="rtu"
),
ModbusTlsParams(host=USER_INPUT[CONF_HOST], port=USER_INPUT[CONF_PORT]),
UNIT_ID,
)
+2 -2
View File
@@ -5,7 +5,7 @@ from typing import Any
from unittest.mock import MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from modbus_connection import ModbusError, ModbusTcpParams
from modbus_connection import ModbusError, ModbusTcpParams, ModbusTlsParams
from modbus_connection.mock import MockModbusConnection
from pystiebeleltron import StiebelEltronModbusError
import pytest
@@ -82,7 +82,7 @@ async def test_async_setup_entry_conflicting_link_settings(
async_get_unit(
hass,
other_entry,
ModbusTcpParams(host="1.1.1.1", port=502, framer="rtu"),
ModbusTlsParams(host="1.1.1.1", port=502),
UNIT_ID,
)
mock_config_entry.add_to_hass(hass)