diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index 5d30362627e..3075ab2d01d 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -14,12 +14,11 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import FritzBoxDeviceEntity -from .const import CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN -from .coordinator import FritzboxDataUpdateCoordinator +from .common import get_coordinator from .model import FritzEntityDescriptionMixinBase @@ -68,18 +67,25 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome binary sensor from ConfigEntry.""" - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - [ - FritzboxBinarySensor(coordinator, ain, description) - for ain, device in coordinator.data.devices.items() - for description in BINARY_SENSOR_TYPES - if description.suitable(device) - ] - ) + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzboxBinarySensor(coordinator, ain, description) + for ain in coordinator.new_devices + for description in BINARY_SENSOR_TYPES + if description.suitable(coordinator.data.devices[ain]) + ] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzboxBinarySensor(FritzBoxDeviceEntity, BinarySensorEntity): diff --git a/homeassistant/components/fritzbox/button.py b/homeassistant/components/fritzbox/button.py index cc5457fb8a2..d5331642325 100644 --- a/homeassistant/components/fritzbox/button.py +++ b/homeassistant/components/fritzbox/button.py @@ -3,25 +3,33 @@ from pyfritzhome.devicetypes import FritzhomeTemplate from homeassistant.components.button import ButtonEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import FritzboxDataUpdateCoordinator, FritzBoxEntity -from .const import CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN +from . import FritzBoxEntity +from .common import get_coordinator +from .const import DOMAIN async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome template from ConfigEntry.""" - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - [FritzBoxTemplate(coordinator, ain) for ain in coordinator.data.templates] - ) + @callback + def _add_entities() -> None: + """Add templates.""" + if not coordinator.new_templates: + return + async_add_entities( + [FritzBoxTemplate(coordinator, ain) for ain in coordinator.new_templates] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzBoxTemplate(FritzBoxEntity, ButtonEntity): @@ -37,7 +45,7 @@ class FritzBoxTemplate(FritzBoxEntity, ButtonEntity): """Return device specific attributes.""" return DeviceInfo( name=self.data.name, - identifiers={(FRITZBOX_DOMAIN, self.ain)}, + identifiers={(DOMAIN, self.ain)}, configuration_url=self.coordinator.configuration_url, manufacturer="AVM", model="SmartHome Template", diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 7c846789637..fa1873bc379 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -18,17 +18,16 @@ from homeassistant.const import ( PRECISION_HALVES, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import FritzboxDataUpdateCoordinator, FritzBoxDeviceEntity +from . import FritzBoxDeviceEntity +from .common import get_coordinator from .const import ( ATTR_STATE_BATTERY_LOW, ATTR_STATE_HOLIDAY_MODE, ATTR_STATE_SUMMER_MODE, ATTR_STATE_WINDOW_OPEN, - CONF_COORDINATOR, - DOMAIN as FRITZBOX_DOMAIN, ) from .model import ClimateExtraAttributes @@ -50,17 +49,24 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome thermostat from ConfigEntry.""" - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - [ - FritzboxThermostat(coordinator, ain) - for ain, device in coordinator.data.devices.items() - if device.has_thermostat - ] - ) + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzboxThermostat(coordinator, ain) + for ain in coordinator.new_devices + if coordinator.data.devices[ain].has_thermostat + ] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity): diff --git a/homeassistant/components/fritzbox/common.py b/homeassistant/components/fritzbox/common.py new file mode 100644 index 00000000000..ab87a51f9ce --- /dev/null +++ b/homeassistant/components/fritzbox/common.py @@ -0,0 +1,16 @@ +"""Common functions for fritzbox integration.""" + +from homeassistant.core import HomeAssistant + +from .const import CONF_COORDINATOR, DOMAIN +from .coordinator import FritzboxDataUpdateCoordinator + + +def get_coordinator( + hass: HomeAssistant, config_entry_id: str +) -> FritzboxDataUpdateCoordinator: + """Get coordinator for given config entry id.""" + coordinator: FritzboxDataUpdateCoordinator = hass.data[DOMAIN][config_entry_id][ + CONF_COORDINATOR + ] + return coordinator diff --git a/homeassistant/components/fritzbox/coordinator.py b/homeassistant/components/fritzbox/coordinator.py index 194825e602f..f6d210e367a 100644 --- a/homeassistant/components/fritzbox/coordinator.py +++ b/homeassistant/components/fritzbox/coordinator.py @@ -37,6 +37,8 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat self.fritz: Fritzhome = hass.data[DOMAIN][self.entry.entry_id][CONF_CONNECTIONS] self.configuration_url = self.fritz.get_prefixed_host() self.has_templates = has_templates + self.new_devices: set[str] = set() + self.new_templates: set[str] = set() super().__init__( hass, @@ -45,6 +47,8 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat update_interval=timedelta(seconds=30), ) + self.data = FritzboxCoordinatorData({}, {}) + def _update_fritz_devices(self) -> FritzboxCoordinatorData: """Update all fritzbox device data.""" try: @@ -87,6 +91,9 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat for template in templates: template_data[template.ain] = template + self.new_devices = device_data.keys() - self.data.devices.keys() + self.new_templates = template_data.keys() - self.data.templates.keys() + return FritzboxCoordinatorData(devices=device_data, templates=template_data) async def _async_update_data(self) -> FritzboxCoordinatorData: diff --git a/homeassistant/components/fritzbox/cover.py b/homeassistant/components/fritzbox/cover.py index df3b1562f9b..431b33f1b49 100644 --- a/homeassistant/components/fritzbox/cover.py +++ b/homeassistant/components/fritzbox/cover.py @@ -10,26 +10,35 @@ from homeassistant.components.cover import ( CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import FritzboxDataUpdateCoordinator, FritzBoxDeviceEntity -from .const import CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN +from . import FritzBoxDeviceEntity +from .common import get_coordinator async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome cover from ConfigEntry.""" - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - FritzboxCover(coordinator, ain) - for ain, device in coordinator.data.devices.items() - if device.has_blind - ) + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzboxCover(coordinator, ain) + for ain in coordinator.new_devices + if coordinator.data.devices[ain].has_blind + ] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzboxCover(FritzBoxDeviceEntity, CoverEntity): diff --git a/homeassistant/components/fritzbox/light.py b/homeassistant/components/fritzbox/light.py index f83dd454592..f78a0a60efc 100644 --- a/homeassistant/components/fritzbox/light.py +++ b/homeassistant/components/fritzbox/light.py @@ -13,17 +13,12 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import FritzboxDataUpdateCoordinator, FritzBoxDeviceEntity -from .const import ( - COLOR_MODE, - COLOR_TEMP_MODE, - CONF_COORDINATOR, - DOMAIN as FRITZBOX_DOMAIN, - LOGGER, -) +from .common import get_coordinator +from .const import COLOR_MODE, COLOR_TEMP_MODE, LOGGER SUPPORTED_COLOR_MODES = {ColorMode.COLOR_TEMP, ColorMode.HS} @@ -32,31 +27,29 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome light from ConfigEntry.""" - entities: list[FritzboxLight] = [] - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - for ain, device in coordinator.data.devices.items(): - if not device.has_lightbulb: - continue - - supported_color_temps = await hass.async_add_executor_job( - device.get_color_temps + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzboxLight( + coordinator, + ain, + device.get_colors(), + device.get_color_temps(), + ) + for ain in coordinator.new_devices + if (device := coordinator.data.devices[ain]).has_lightbulb + ] ) - supported_colors = await hass.async_add_executor_job(device.get_colors) + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) - entities.append( - FritzboxLight( - coordinator, - ain, - supported_colors, - supported_color_temps, - ) - ) - - async_add_entities(entities) + _add_entities() class FritzboxLight(FritzBoxDeviceEntity, LightEntity): diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 013c1dfc7b5..35456b7598b 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -25,13 +25,13 @@ from homeassistant.const import ( UnitOfPower, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util.dt import utc_from_timestamp from . import FritzBoxDeviceEntity -from .const import CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN +from .common import get_coordinator from .model import FritzEntityDescriptionMixinBase @@ -212,16 +212,25 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome sensor from ConfigEntry.""" - coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - [ - FritzBoxSensor(coordinator, ain, description) - for ain, device in coordinator.data.devices.items() - for description in SENSOR_TYPES - if description.suitable(device) - ] - ) + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzBoxSensor(coordinator, ain, description) + for ain in coordinator.new_devices + for description in SENSOR_TYPES + if description.suitable(coordinator.data.devices[ain]) + ] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzBoxSensor(FritzBoxDeviceEntity, SensorEntity): diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 5eee3019633..cf7dcb6e3b9 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -5,28 +5,35 @@ from typing import Any from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import FritzboxDataUpdateCoordinator, FritzBoxDeviceEntity -from .const import CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN +from . import FritzBoxDeviceEntity +from .common import get_coordinator async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome switch from ConfigEntry.""" - coordinator: FritzboxDataUpdateCoordinator = hass.data[FRITZBOX_DOMAIN][ - entry.entry_id - ][CONF_COORDINATOR] + coordinator = get_coordinator(hass, entry.entry_id) - async_add_entities( - [ - FritzboxSwitch(coordinator, ain) - for ain, device in coordinator.data.devices.items() - if device.has_switch - ] - ) + @callback + def _add_entities() -> None: + """Add devices.""" + if not coordinator.new_devices: + return + async_add_entities( + [ + FritzboxSwitch(coordinator, ain) + for ain in coordinator.new_devices + if coordinator.data.devices[ain].has_switch + ] + ) + + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + _add_entities() class FritzboxSwitch(FritzBoxDeviceEntity, SwitchEntity): diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index 15ff04f3720..1faf37c84ee 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -45,6 +45,17 @@ async def setup_config_entry( return result +def set_devices( + fritz: Mock, devices: list[Mock] | None = None, templates: list[Mock] | None = None +) -> None: + """Set list of devices or templates.""" + if devices is not None: + fritz().get_devices.return_value = devices + + if templates is not None: + fritz().get_templates.return_value = templates + + class FritzEntityBaseMock(Mock): """base mock of a AVM Fritz!Box binary sensor device.""" diff --git a/tests/components/fritzbox/test_binary_sensor.py b/tests/components/fritzbox/test_binary_sensor.py index ac6b702147a..983516bb9c0 100644 --- a/tests/components/fritzbox/test_binary_sensor.py +++ b/tests/components/fritzbox/test_binary_sensor.py @@ -21,7 +21,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from . import FritzDeviceBinarySensorMock, setup_config_entry +from . import FritzDeviceBinarySensorMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -126,3 +126,26 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceBinarySensorMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(f"{ENTITY_ID}_alarm") + assert state + + new_device = FritzDeviceBinarySensorMock() + new_device.ain = "7890 1234" + new_device.name = "new_device" + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_device_alarm") + assert state diff --git a/tests/components/fritzbox/test_button.py b/tests/components/fritzbox/test_button.py index 9c53c895f5d..8c0bbec573e 100644 --- a/tests/components/fritzbox/test_button.py +++ b/tests/components/fritzbox/test_button.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box templates.""" +from datetime import timedelta from unittest.mock import Mock from homeassistant.components.button import DOMAIN, SERVICE_PRESS @@ -10,10 +11,13 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant +import homeassistant.util.dt as dt_util -from . import FritzEntityBaseMock, setup_config_entry +from . import FritzEntityBaseMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG +from tests.common import async_fire_time_changed + ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}" @@ -41,3 +45,26 @@ async def test_apply_template(hass: HomeAssistant, fritz: Mock) -> None: DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: ENTITY_ID}, True ) assert fritz().apply_template.call_count == 1 + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + template = FritzEntityBaseMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], fritz=fritz, template=template + ) + + state = hass.states.get(ENTITY_ID) + assert state + + new_template = FritzEntityBaseMock() + new_template.ain = "7890 1234" + new_template.name = "new_template" + set_devices(fritz, templates=[template, new_template]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_template") + assert state diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index d49b5710a12..a14c53d6529 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -41,7 +41,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from . import FritzDeviceClimateMock, setup_config_entry +from . import FritzDeviceClimateMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -402,3 +402,26 @@ async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: assert fritz().update_devices.call_count == 3 assert state assert state.attributes[ATTR_PRESET_MODE] == PRESET_ECO + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceClimateMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(ENTITY_ID) + assert state + + new_device = FritzDeviceClimateMock() + new_device.ain = "7890 1234" + new_device.name = "new_climate" + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_climate") + assert state diff --git a/tests/components/fritzbox/test_cover.py b/tests/components/fritzbox/test_cover.py index af725ce93da..e3a6d786abf 100644 --- a/tests/components/fritzbox/test_cover.py +++ b/tests/components/fritzbox/test_cover.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box switch component.""" +from datetime import timedelta from unittest.mock import Mock, call from homeassistant.components.cover import ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN @@ -12,10 +13,13 @@ from homeassistant.const import ( SERVICE_STOP_COVER, ) from homeassistant.core import HomeAssistant +import homeassistant.util.dt as dt_util -from . import FritzDeviceCoverMock, setup_config_entry +from . import FritzDeviceCoverMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG +from tests.common import async_fire_time_changed + ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}" @@ -84,3 +88,26 @@ async def test_stop_cover(hass: HomeAssistant, fritz: Mock) -> None: DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: ENTITY_ID}, True ) assert device.set_blind_stop.call_count == 1 + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceCoverMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(ENTITY_ID) + assert state + + new_device = FritzDeviceCoverMock() + new_device.ain = "7890 1234" + new_device.name = "new_climate" + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_climate") + assert state diff --git a/tests/components/fritzbox/test_light.py b/tests/components/fritzbox/test_light.py index 5511b93ac3f..858b564cd18 100644 --- a/tests/components/fritzbox/test_light.py +++ b/tests/components/fritzbox/test_light.py @@ -29,7 +29,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from . import FritzDeviceLightMock, setup_config_entry +from . import FritzDeviceLightMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -262,3 +262,38 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceLightMock() + device.get_color_temps.return_value = [2700, 6500] + device.get_colors.return_value = { + "Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")] + } + device.color_mode = COLOR_TEMP_MODE + device.color_temp = 2700 + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(ENTITY_ID) + assert state + + new_device = FritzDeviceLightMock() + new_device.ain = "7890 1234" + new_device.name = "new_light" + new_device.get_color_temps.return_value = [2700, 6500] + new_device.get_colors.return_value = { + "Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")] + } + new_device.color_mode = COLOR_TEMP_MODE + new_device.color_temp = 2700 + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_light") + assert state diff --git a/tests/components/fritzbox/test_sensor.py b/tests/components/fritzbox/test_sensor.py index b363d966c01..9fe25d02ed0 100644 --- a/tests/components/fritzbox/test_sensor.py +++ b/tests/components/fritzbox/test_sensor.py @@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util -from . import FritzDeviceSensorMock, setup_config_entry +from . import FritzDeviceSensorMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -108,3 +108,26 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceSensorMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(f"{ENTITY_ID}_temperature") + assert state + + new_device = FritzDeviceSensorMock() + new_device.ain = "7890 1234" + new_device.name = "new_device" + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_device_temperature") + assert state diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index 4ed1a88190a..aefe21e3ffc 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -31,7 +31,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util -from . import FritzDeviceSwitchMock, setup_config_entry +from . import FritzDeviceSwitchMock, set_devices, setup_config_entry from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -187,3 +187,26 @@ async def test_assume_device_unavailable(hass: HomeAssistant, fritz: Mock) -> No state = hass.states.get(ENTITY_ID) assert state assert state.state == STATE_UNAVAILABLE + + +async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: + """Test adding new discovered devices during runtime.""" + device = FritzDeviceSwitchMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + state = hass.states.get(ENTITY_ID) + assert state + + new_device = FritzDeviceSwitchMock() + new_device.ain = "7890 1234" + new_device.name = "new_switch" + set_devices(fritz, devices=[device, new_device]) + + next_update = dt_util.utcnow() + timedelta(seconds=200) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.new_switch") + assert state