Do not start modbus update process until connection+delay. (#150796)

This commit is contained in:
jan iversen
2025-08-18 17:20:41 +02:00
committed by GitHub
parent 9910480980
commit 53ca369395
4 changed files with 42 additions and 26 deletions

View File

@@ -92,7 +92,6 @@ class BasePlatform(Entity):
self._scan_interval = int(entry[CONF_SCAN_INTERVAL])
self._cancel_timer: Callable[[], None] | None = None
self._cancel_call: Callable[[], None] | None = None
self._attr_unique_id = entry.get(CONF_UNIQUE_ID)
self._attr_name = entry[CONF_NAME]
self._attr_device_class = entry.get(CONF_DEVICE_CLASS)
@@ -177,9 +176,20 @@ class BasePlatform(Entity):
self._attr_available = False
self.async_write_ha_state()
async def async_await_connection(self, _now: Any) -> None:
"""Wait for first connect."""
await self._hub.event_connected.wait()
self.async_run()
async def async_base_added_to_hass(self) -> None:
"""Handle entity which will be added."""
self.async_run()
self.async_on_remove(
async_call_later(
self.hass,
self._hub.config_delay + 0.1,
self.async_await_connection,
)
)
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_STOP_ENTITY, self.async_hold)
)

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio
from collections import namedtuple
from collections.abc import Callable
from typing import Any
from pymodbus.client import (
@@ -28,11 +27,10 @@ from homeassistant.const import (
CONF_TYPE,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
@@ -254,13 +252,13 @@ class ModbusHub:
self._client: (
AsyncModbusSerialClient | AsyncModbusTcpClient | AsyncModbusUdpClient | None
) = None
self._async_cancel_listener: Callable[[], None] | None = None
self._in_error = False
self._lock = asyncio.Lock()
self.event_connected = asyncio.Event()
self.hass = hass
self.name = client_config[CONF_NAME]
self._config_type = client_config[CONF_TYPE]
self._config_delay = client_config[CONF_DELAY]
self.config_delay = client_config[CONF_DELAY]
self._pb_request: dict[str, RunEntry] = {}
self._connect_task: asyncio.Task
self._last_log_error: str = ""
@@ -325,10 +323,10 @@ class ModbusHub:
_LOGGER.info(message)
# Start counting down to allow modbus requests.
if self._config_delay:
self._async_cancel_listener = async_call_later(
self.hass, self._config_delay, self.async_end_delay
)
if self.config_delay:
await asyncio.sleep(self.config_delay)
self.config_delay = 0
self.event_connected.set()
async def async_setup(self) -> bool:
"""Set up pymodbus client."""
@@ -349,12 +347,6 @@ class ModbusHub:
)
return True
@callback
def async_end_delay(self, args: Any) -> None:
"""End startup delay."""
self._async_cancel_listener = None
self._config_delay = 0
async def async_restart(self) -> None:
"""Reconnect client."""
if self._client:
@@ -364,9 +356,6 @@ class ModbusHub:
async def async_close(self) -> None:
"""Disconnect client."""
if self._async_cancel_listener:
self._async_cancel_listener()
self._async_cancel_listener = None
if not self._connect_task.done():
self._connect_task.cancel()
@@ -426,8 +415,6 @@ class ModbusHub:
use_call: str,
) -> ModbusPDU | None:
"""Convert async to sync pymodbus call."""
if self._config_delay:
return None
async with self._lock:
if not self._client:
return None

View File

@@ -23,6 +23,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from homeassistant.util.hass_dict import HassKey
from tests.common import async_fire_time_changed, mock_restore_cache
@@ -121,6 +122,7 @@ def mock_pymodbus_fixture(do_exception, register_words):
async def mock_modbus_fixture(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory,
check_config_loaded,
config_addon,
do_config,
@@ -158,6 +160,15 @@ async def mock_modbus_fixture(
result = await async_setup_component(hass, DOMAIN, config)
assert result or not check_config_loaded
await hass.async_block_till_done()
key = HassKey(DOMAIN)
if key not in hass.data:
return None
hub = hass.data[HassKey(DOMAIN)][TEST_MODBUS_NAME]
await hub.event_connected.wait()
assert hub.event_connected.is_set()
freezer.tick(timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
return mock_pymodbus

View File

@@ -920,6 +920,9 @@ async def mock_modbus_read_pymodbus_fixture(
freezer.tick(timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60))
async_fire_time_changed(hass)
await hass.async_block_till_done()
freezer.tick(timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60))
async_fire_time_changed(hass)
await hass.async_block_till_done()
return mock_pymodbus
@@ -1088,11 +1091,11 @@ async def test_delay(
start_time = dt_util.utcnow()
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNKNOWN
assert hass.states.get(entity_id).state in (STATE_UNKNOWN, STATE_UNAVAILABLE)
time_sensor_active = start_time + timedelta(seconds=2)
time_after_delay = start_time + timedelta(seconds=(set_delay))
time_after_scan = start_time + timedelta(seconds=(set_delay + set_scan_interval))
time_after_scan = time_after_delay + timedelta(seconds=(set_scan_interval))
time_stop = time_after_scan + timedelta(seconds=10)
now = start_time
while now < time_stop:
@@ -1105,8 +1108,13 @@ async def test_delay(
await hass.async_block_till_done()
if now > time_sensor_active:
if now <= time_after_delay:
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
elif now > time_after_scan:
assert hass.states.get(entity_id).state in (
STATE_UNKNOWN,
STATE_UNAVAILABLE,
)
if now <= time_after_delay + timedelta(seconds=2):
continue
if now > time_after_scan + timedelta(seconds=2):
assert hass.states.get(entity_id).state == STATE_ON