From 475a2fb8284819b606597f86920447632b1c42e6 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:53:31 +0100 Subject: [PATCH] Discover new devices at runtime in onewire (#135199) --- homeassistant/components/onewire/__init__.py | 2 + .../components/onewire/binary_sensor.py | 32 +++++++++++--- .../components/onewire/onewirehub.py | 43 +++++++++++++++++-- .../components/onewire/quality_scale.yaml | 4 +- homeassistant/components/onewire/select.py | 32 +++++++++++--- homeassistant/components/onewire/sensor.py | 40 ++++++++++++----- homeassistant/components/onewire/switch.py | 32 +++++++++++--- .../components/onewire/test_binary_sensor.py | 33 +++++++++++++- tests/components/onewire/test_init.py | 29 ++++++++++++- tests/components/onewire/test_select.py | 31 ++++++++++++- tests/components/onewire/test_sensor.py | 33 +++++++++++++- tests/components/onewire/test_switch.py | 33 +++++++++++++- 12 files changed, 299 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index 753960f0ae3..c77d87d91b9 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -37,6 +37,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: OneWireConfigEntry) -> b await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS) + onewire_hub.schedule_scan_for_new_devices() + entry.async_on_unload(entry.add_update_listener(options_update_listener)) return True diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index d9e21ce013d..7a8f81eec0e 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -13,11 +13,17 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, READ_MODE_BOOL from .entity import OneWireEntity, OneWireEntityDescription -from .onewirehub import OneWireConfigEntry, OneWireHub +from .onewirehub import ( + SIGNAL_NEW_DEVICE_CONNECTED, + OneWireConfigEntry, + OneWireHub, + OWDeviceDescription, +) # the library uses non-persistent connections # and concurrent access to the bus is managed by the server @@ -98,16 +104,28 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - async_add_entities(get_entities(config_entry.runtime_data), True) + + async def _add_entities( + hub: OneWireHub, devices: list[OWDeviceDescription] + ) -> None: + """Add 1-Wire entities for all devices.""" + if not devices: + return + async_add_entities(get_entities(hub, devices), True) + + hub = config_entry.runtime_data + await _add_entities(hub, hub.devices) + config_entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_NEW_DEVICE_CONNECTED, _add_entities) + ) -def get_entities(onewire_hub: OneWireHub) -> list[OneWireBinarySensor]: +def get_entities( + onewire_hub: OneWireHub, devices: list[OWDeviceDescription] +) -> list[OneWireBinarySensor]: """Get a list of entities.""" - if not onewire_hub.devices: - return [] - entities: list[OneWireBinarySensor] = [] - for device in onewire_hub.devices: + for device in devices: family = device.family device_id = device.id device_type = device.type diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py index deeaaa6283d..a8d8dd06034 100644 --- a/homeassistant/components/onewire/onewirehub.py +++ b/homeassistant/components/onewire/onewirehub.py @@ -2,6 +2,7 @@ from __future__ import annotations +from datetime import datetime, timedelta import logging import os @@ -9,9 +10,12 @@ from pyownet import protocol from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_VIA_DEVICE, CONF_HOST, CONF_PORT -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util.signal_type import SignalType from .const import ( DEVICE_SUPPORT, @@ -32,10 +36,15 @@ DEVICE_MANUFACTURER = { "EF": MANUFACTURER_HOBBYBOARDS, } +_DEVICE_SCAN_INTERVAL = timedelta(minutes=5) _LOGGER = logging.getLogger(__name__) type OneWireConfigEntry = ConfigEntry[OneWireHub] +SIGNAL_NEW_DEVICE_CONNECTED = SignalType["OneWireHub", list[OWDeviceDescription]]( + f"{DOMAIN}_new_device_connected" +) + def _is_known_device(device_family: str, device_type: str | None) -> bool: """Check if device family/type is known to the library.""" @@ -69,14 +78,42 @@ class OneWireHub: async def initialize(self) -> None: """Initialize a config entry.""" await self._hass.async_add_executor_job(self._initialize) - # Populate the device registry + self._populate_device_registry(self.devices) + + @callback + def _populate_device_registry(self, devices: list[OWDeviceDescription]) -> None: + """Populate the device registry.""" device_registry = dr.async_get(self._hass) - for device in self.devices: + for device in devices: device_registry.async_get_or_create( config_entry_id=self._config_entry.entry_id, **device.device_info, ) + def schedule_scan_for_new_devices(self) -> None: + """Schedule a regular scan of the bus for new devices.""" + self._config_entry.async_on_unload( + async_track_time_interval( + self._hass, self._scan_for_new_devices, _DEVICE_SCAN_INTERVAL + ) + ) + + async def _scan_for_new_devices(self, _: datetime) -> None: + """Scan the bus for new devices.""" + devices = await self._hass.async_add_executor_job( + _discover_devices, self.owproxy + ) + existing_device_ids = [device.id for device in self.devices] + new_devices = [ + device for device in devices if device.id not in existing_device_ids + ] + if new_devices: + self.devices.extend(new_devices) + self._populate_device_registry(new_devices) + async_dispatcher_send( + self._hass, SIGNAL_NEW_DEVICE_CONNECTED, self, new_devices + ) + def _discover_devices( owproxy: protocol._Proxy, path: str = "/", parent_id: str | None = None diff --git a/homeassistant/components/onewire/quality_scale.yaml b/homeassistant/components/onewire/quality_scale.yaml index a262f9cd714..9e706c16607 100644 --- a/homeassistant/components/onewire/quality_scale.yaml +++ b/homeassistant/components/onewire/quality_scale.yaml @@ -84,8 +84,8 @@ rules: comment: It doesn't make sense to override defaults reconfiguration-flow: done dynamic-devices: - status: todo - comment: Not yet implemented + status: done + comment: The bus is scanned for new devices at regular interval discovery-update-info: status: todo comment: Under review diff --git a/homeassistant/components/onewire/select.py b/homeassistant/components/onewire/select.py index 108cc7b2ec8..7a26ecdbb31 100644 --- a/homeassistant/components/onewire/select.py +++ b/homeassistant/components/onewire/select.py @@ -9,11 +9,17 @@ import os from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import READ_MODE_INT from .entity import OneWireEntity, OneWireEntityDescription -from .onewirehub import OneWireConfigEntry, OneWireHub +from .onewirehub import ( + SIGNAL_NEW_DEVICE_CONNECTED, + OneWireConfigEntry, + OneWireHub, + OWDeviceDescription, +) # the library uses non-persistent connections # and concurrent access to the bus is managed by the server @@ -45,17 +51,29 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - async_add_entities(get_entities(config_entry.runtime_data), True) + + async def _add_entities( + hub: OneWireHub, devices: list[OWDeviceDescription] + ) -> None: + """Add 1-Wire entities for all devices.""" + if not devices: + return + async_add_entities(get_entities(hub, devices), True) + + hub = config_entry.runtime_data + await _add_entities(hub, hub.devices) + config_entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_NEW_DEVICE_CONNECTED, _add_entities) + ) -def get_entities(onewire_hub: OneWireHub) -> list[OneWireSelectEntity]: +def get_entities( + onewire_hub: OneWireHub, devices: list[OWDeviceDescription] +) -> list[OneWireSelectEntity]: """Get a list of entities.""" - if not onewire_hub.devices: - return [] - entities: list[OneWireSelectEntity] = [] - for device in onewire_hub.devices: + for device in devices: family = device.family device_id = device.id device_info = device.device_info diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 0f430e1be35..ae6a3642c58 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -26,6 +26,7 @@ from homeassistant.const import ( UnitOfTemperature, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -39,7 +40,12 @@ from .const import ( READ_MODE_INT, ) from .entity import OneWireEntity, OneWireEntityDescription -from .onewirehub import OneWireConfigEntry, OneWireHub +from .onewirehub import ( + SIGNAL_NEW_DEVICE_CONNECTED, + OneWireConfigEntry, + OneWireHub, + OWDeviceDescription, +) # the library uses non-persistent connections # and concurrent access to the bus is managed by the server @@ -357,23 +363,35 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - # note: we have to go through the executor as SENSOR platform - # makes extra calls to the hub during device listing - entities = await hass.async_add_executor_job( - get_entities, config_entry.runtime_data, config_entry.options + + async def _add_entities( + hub: OneWireHub, devices: list[OWDeviceDescription] + ) -> None: + """Add 1-Wire entities for all devices.""" + if not devices: + return + # note: we have to go through the executor as SENSOR platform + # makes extra calls to the hub during device listing + entities = await hass.async_add_executor_job( + get_entities, hub, devices, config_entry.options + ) + async_add_entities(entities, True) + + hub = config_entry.runtime_data + await _add_entities(hub, hub.devices) + config_entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_NEW_DEVICE_CONNECTED, _add_entities) ) - async_add_entities(entities, True) def get_entities( - onewire_hub: OneWireHub, options: MappingProxyType[str, Any] + onewire_hub: OneWireHub, + devices: list[OWDeviceDescription], + options: MappingProxyType[str, Any], ) -> list[OneWireSensor]: """Get a list of entities.""" - if not onewire_hub.devices: - return [] - entities: list[OneWireSensor] = [] - for device in onewire_hub.devices: + for device in devices: family = device.family device_type = device.type device_id = device.id diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index b2cdec014f6..0df2ba0dd57 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -10,11 +10,17 @@ from typing import Any from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, READ_MODE_BOOL from .entity import OneWireEntity, OneWireEntityDescription -from .onewirehub import OneWireConfigEntry, OneWireHub +from .onewirehub import ( + SIGNAL_NEW_DEVICE_CONNECTED, + OneWireConfigEntry, + OneWireHub, + OWDeviceDescription, +) # the library uses non-persistent connections # and concurrent access to the bus is managed by the server @@ -158,17 +164,29 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - async_add_entities(get_entities(config_entry.runtime_data), True) + + async def _add_entities( + hub: OneWireHub, devices: list[OWDeviceDescription] + ) -> None: + """Add 1-Wire entities for all devices.""" + if not devices: + return + async_add_entities(get_entities(hub, devices), True) + + hub = config_entry.runtime_data + await _add_entities(hub, hub.devices) + config_entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_NEW_DEVICE_CONNECTED, _add_entities) + ) -def get_entities(onewire_hub: OneWireHub) -> list[OneWireSwitch]: +def get_entities( + onewire_hub: OneWireHub, devices: list[OWDeviceDescription] +) -> list[OneWireSwitch]: """Get a list of entities.""" - if not onewire_hub.devices: - return [] - entities: list[OneWireSwitch] = [] - for device in onewire_hub.devices: + for device in devices: family = device.family device_type = device.type device_id = device.id diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index fb50c9dc367..dd2f3874e36 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -3,9 +3,11 @@ from collections.abc import Generator from unittest.mock import MagicMock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.onewire.onewirehub import _DEVICE_SCAN_INTERVAL from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -13,7 +15,7 @@ from homeassistant.helpers import entity_registry as er from . import setup_owproxy_mock_devices from .const import MOCK_OWPROXY_DEVICES -from tests.common import MockConfigEntry, snapshot_platform +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.fixture(autouse=True) @@ -31,8 +33,35 @@ async def test_binary_sensors( entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: - """Test for 1-Wire binary sensors.""" + """Test for 1-Wire binary sensor entities.""" setup_owproxy_mock_devices(owproxy, MOCK_OWPROXY_DEVICES.keys()) await hass.config_entries.async_setup(config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) + + +@pytest.mark.parametrize("device_id", ["29.111111111111"]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_binary_sensors_delayed( + hass: HomeAssistant, + config_entry: MockConfigEntry, + owproxy: MagicMock, + device_id: str, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test for delayed 1-Wire binary sensor entities.""" + setup_owproxy_mock_devices(owproxy, []) + await hass.config_entries.async_setup(config_entry.entry_id) + + assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + + setup_owproxy_mock_devices(owproxy, [device_id]) + freezer.tick(_DEVICE_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 8 + ) diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py index e417eea8748..0748481c40b 100644 --- a/tests/components/onewire/test_init.py +++ b/tests/components/onewire/test_init.py @@ -3,11 +3,13 @@ from copy import deepcopy from unittest.mock import MagicMock, patch +from freezegun.api import FrozenDateTimeFactory from pyownet import protocol import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.onewire.const import DOMAIN +from homeassistant.components.onewire.onewirehub import _DEVICE_SCAN_INTERVAL from homeassistant.config_entries import ConfigEntryState from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -17,7 +19,7 @@ from homeassistant.setup import async_setup_component from . import setup_owproxy_mock_devices from .const import MOCK_OWPROXY_DEVICES -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed from tests.typing import WebSocketGenerator @@ -102,6 +104,31 @@ async def test_registry( assert device_entry == snapshot(name=f"{device_entry.name}-entry") +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_registry_delayed( + hass: HomeAssistant, + config_entry: MockConfigEntry, + owproxy: MagicMock, + device_registry: dr.DeviceRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test device are correctly registered.""" + setup_owproxy_mock_devices(owproxy, []) + await hass.config_entries.async_setup(config_entry.entry_id) + + assert not dr.async_entries_for_config_entry(device_registry, config_entry.entry_id) + + setup_owproxy_mock_devices(owproxy, ["1F.111111111111"]) + freezer.tick(_DEVICE_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert ( + len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)) + == 2 + ) + + @patch("homeassistant.components.onewire._PLATFORMS", [Platform.SENSOR]) async def test_registry_cleanup( hass: HomeAssistant, diff --git a/tests/components/onewire/test_select.py b/tests/components/onewire/test_select.py index 0a594e2c076..6e1c3277c73 100644 --- a/tests/components/onewire/test_select.py +++ b/tests/components/onewire/test_select.py @@ -3,9 +3,11 @@ from collections.abc import Generator from unittest.mock import MagicMock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.onewire.onewirehub import _DEVICE_SCAN_INTERVAL from homeassistant.components.select import ( DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, @@ -17,7 +19,7 @@ from homeassistant.helpers import entity_registry as er from . import setup_owproxy_mock_devices from .const import MOCK_OWPROXY_DEVICES -from tests.common import MockConfigEntry, snapshot_platform +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.fixture(autouse=True) @@ -42,6 +44,33 @@ async def test_selects( await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) +@pytest.mark.parametrize("device_id", ["28.111111111111"]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_selects_delayed( + hass: HomeAssistant, + config_entry: MockConfigEntry, + owproxy: MagicMock, + device_id: str, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test for delayed 1-Wire select entities.""" + setup_owproxy_mock_devices(owproxy, []) + await hass.config_entries.async_setup(config_entry.entry_id) + + assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + + setup_owproxy_mock_devices(owproxy, [device_id]) + freezer.tick(_DEVICE_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 1 + ) + + @pytest.mark.parametrize("device_id", ["28.111111111111"]) async def test_selection_option_service( hass: HomeAssistant, diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 9b0d4ea8ca6..f1ef2dfa11b 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -5,10 +5,12 @@ from copy import deepcopy import logging from unittest.mock import MagicMock, _patch_dict, patch +from freezegun.api import FrozenDateTimeFactory from pyownet.protocol import OwnetError import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.onewire.onewirehub import _DEVICE_SCAN_INTERVAL from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -16,7 +18,7 @@ from homeassistant.helpers import entity_registry as er from . import setup_owproxy_mock_devices from .const import ATTR_INJECT_READS, MOCK_OWPROXY_DEVICES -from tests.common import MockConfigEntry, snapshot_platform +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.fixture(autouse=True) @@ -34,13 +36,40 @@ async def test_sensors( entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: - """Test for 1-Wire sensors.""" + """Test for 1-Wire sensor entities.""" setup_owproxy_mock_devices(owproxy, MOCK_OWPROXY_DEVICES.keys()) await hass.config_entries.async_setup(config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) +@pytest.mark.parametrize("device_id", ["12.111111111111"]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors_delayed( + hass: HomeAssistant, + config_entry: MockConfigEntry, + owproxy: MagicMock, + device_id: str, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test for delayed 1-Wire sensor entities.""" + setup_owproxy_mock_devices(owproxy, []) + await hass.config_entries.async_setup(config_entry.entry_id) + + assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + + setup_owproxy_mock_devices(owproxy, [device_id]) + freezer.tick(_DEVICE_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 2 + ) + + @pytest.mark.parametrize("device_id", ["12.111111111111"]) async def test_tai8570_sensors( hass: HomeAssistant, diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 6bd76d89184..ca13a69e2da 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -3,9 +3,11 @@ from collections.abc import Generator from unittest.mock import MagicMock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.onewire.onewirehub import _DEVICE_SCAN_INTERVAL from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -20,7 +22,7 @@ from homeassistant.helpers import entity_registry as er from . import setup_owproxy_mock_devices from .const import MOCK_OWPROXY_DEVICES -from tests.common import MockConfigEntry, snapshot_platform +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.fixture(autouse=True) @@ -38,13 +40,40 @@ async def test_switches( entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: - """Test for 1-Wire switches.""" + """Test for 1-Wire switch entities.""" setup_owproxy_mock_devices(owproxy, MOCK_OWPROXY_DEVICES.keys()) await hass.config_entries.async_setup(config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) +@pytest.mark.parametrize("device_id", ["05.111111111111"]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_switches_delayed( + hass: HomeAssistant, + config_entry: MockConfigEntry, + owproxy: MagicMock, + device_id: str, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test for delayed 1-Wire switch entities.""" + setup_owproxy_mock_devices(owproxy, []) + await hass.config_entries.async_setup(config_entry.entry_id) + + assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + + setup_owproxy_mock_devices(owproxy, [device_id]) + freezer.tick(_DEVICE_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 1 + ) + + @pytest.mark.parametrize("device_id", ["05.111111111111"]) @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_switch_toggle(