From 4b5996c5ed76ffc1046445485bcd643f05f41c17 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:26:35 +0200 Subject: [PATCH] Drop support for Tradfri groups and YAML configuration (#68033) * Drop support for Tradfri groups * Remove context * Remove async_setup * Mark removed * Pass generator expression --- homeassistant/components/tradfri/__init__.py | 86 +------------ homeassistant/components/tradfri/const.py | 8 +- .../components/tradfri/coordinator.py | 45 ------- .../components/tradfri/diagnostics.py | 3 +- homeassistant/components/tradfri/light.py | 78 +----------- tests/components/tradfri/common.py | 1 - tests/components/tradfri/test_config_flow.py | 120 ------------------ tests/components/tradfri/test_diagnostics.py | 7 - tests/components/tradfri/test_init.py | 2 - tests/components/tradfri/test_light.py | 89 ------------- 10 files changed, 8 insertions(+), 431 deletions(-) diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 0054b1d7bff..26637f500ac 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -9,10 +9,7 @@ from pytradfri import Gateway, RequestError from pytradfri.api.aiocoap_api import APIFactory from pytradfri.command import Command from pytradfri.device import Device -from pytradfri.group import Group -import voluptuous as vol -from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -24,82 +21,30 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType from .const import ( ATTR_TRADFRI_GATEWAY, ATTR_TRADFRI_GATEWAY_MODEL, ATTR_TRADFRI_MANUFACTURER, - CONF_ALLOW_TRADFRI_GROUPS, CONF_GATEWAY_ID, CONF_IDENTITY, - CONF_IMPORT_GROUPS, CONF_KEY, COORDINATOR, COORDINATOR_LIST, - DEFAULT_ALLOW_TRADFRI_GROUPS, DOMAIN, - GROUPS_LIST, KEY_API, PLATFORMS, SIGNAL_GW, TIMEOUT_API, ) -from .coordinator import ( - TradfriDeviceDataUpdateCoordinator, - TradfriGroupDataUpdateCoordinator, -) +from .coordinator import TradfriDeviceDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) FACTORY = "tradfri_factory" LISTENERS = "tradfri_listeners" -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - vol.All( - cv.deprecated(CONF_HOST), - cv.deprecated( - CONF_ALLOW_TRADFRI_GROUPS, - ), - { - vol.Optional(CONF_HOST): cv.string, - vol.Optional( - CONF_ALLOW_TRADFRI_GROUPS, default=DEFAULT_ALLOW_TRADFRI_GROUPS - ): cv.boolean, - }, - ), - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Tradfri component.""" - if (conf := config.get(DOMAIN)) is None: - return True - - configured_hosts = [ - entry.data.get("host") for entry in hass.config_entries.async_entries(DOMAIN) - ] - - host = conf.get(CONF_HOST) - import_groups = conf[CONF_ALLOW_TRADFRI_GROUPS] - - if host is None or host in configured_hosts: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: host, CONF_IMPORT_GROUPS: import_groups}, - ) - ) - - return True +CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) async def async_setup_entry( @@ -127,7 +72,6 @@ async def async_setup_entry( api = factory.request gateway = Gateway() - groups: list[Group] = [] try: gateway_info = await api(gateway.get_gateway_info(), timeout=TIMEOUT_API) @@ -136,19 +80,6 @@ async def async_setup_entry( ) devices: list[Device] = await api(devices_commands, timeout=TIMEOUT_API) - if entry.data[CONF_IMPORT_GROUPS]: - # Note: we should update this page when deprecating: - # https://www.home-assistant.io/integrations/tradfri/ - _LOGGER.warning( - "Importing of Tradfri groups has been deprecated due to stability issues " - "and will be removed in Home Assistant core 2022.5" - ) - # No need to load groups if the user hasn't requested it - groups_commands: Command = await api( - gateway.get_groups(), timeout=TIMEOUT_API - ) - groups = await api(groups_commands, timeout=TIMEOUT_API) - except RequestError as exc: await factory.shutdown() raise ConfigEntryNotReady from exc @@ -172,7 +103,6 @@ async def async_setup_entry( CONF_GATEWAY_ID: gateway, KEY_API: api, COORDINATOR_LIST: [], - GROUPS_LIST: [], } for device in devices: @@ -186,18 +116,6 @@ async def async_setup_entry( ) coordinator_data[COORDINATOR_LIST].append(coordinator) - for group in groups: - group_coordinator = TradfriGroupDataUpdateCoordinator( - hass=hass, api=api, group=group - ) - await group_coordinator.async_config_entry_first_refresh() - entry.async_on_unload( - async_dispatcher_connect( - hass, SIGNAL_GW, group_coordinator.set_hub_available - ) - ) - coordinator_data[GROUPS_LIST].append(group_coordinator) - tradfri_data[COORDINATOR] = coordinator_data async def async_keep_alive(now: datetime) -> None: diff --git a/homeassistant/components/tradfri/const.py b/homeassistant/components/tradfri/const.py index 3d68ebbaee0..d3f04f4e2c4 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -1,7 +1,7 @@ """Consts used by Tradfri.""" from typing import Final -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION +from homeassistant.components.light import SUPPORT_TRANSITION from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import CONF_HOST, Platform, @@ -16,19 +16,16 @@ ATTR_TRADFRI_GATEWAY_MODEL = "E1526" ATTR_TRADFRI_MANUFACTURER = "IKEA of Sweden" ATTR_TRANSITION_TIME = "transition_time" ATTR_MODEL = "model" -CONF_ALLOW_TRADFRI_GROUPS = "allow_tradfri_groups" CONF_IDENTITY = "identity" CONF_IMPORT_GROUPS = "import_groups" CONF_GATEWAY_ID = "gateway_id" CONF_KEY = "key" -DEFAULT_ALLOW_TRADFRI_GROUPS = False + DOMAIN = "tradfri" KEY_API = "tradfri_api" DEVICES = "tradfri_devices" -GROUPS = "tradfri_groups" SIGNAL_GW = "tradfri.gw_status" KEY_SECURITY_CODE = "security_code" -SUPPORTED_GROUP_FEATURES = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION SUPPORTED_LIGHT_FEATURES = SUPPORT_TRANSITION PLATFORMS = [ Platform.COVER, @@ -44,6 +41,5 @@ SCAN_INTERVAL = 60 # Interval for updating the coordinator COORDINATOR = "coordinator" COORDINATOR_LIST = "coordinator_list" -GROUPS_LIST = "groups_list" ATTR_FILTER_LIFE_REMAINING: Final = "filter_life_remaining" diff --git a/homeassistant/components/tradfri/coordinator.py b/homeassistant/components/tradfri/coordinator.py index 5a516e8f46e..8aed96d344f 100644 --- a/homeassistant/components/tradfri/coordinator.py +++ b/homeassistant/components/tradfri/coordinator.py @@ -9,7 +9,6 @@ from typing import Any from pytradfri.command import Command from pytradfri.device import Device from pytradfri.error import RequestError -from pytradfri.group import Group from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -95,47 +94,3 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): self.update_interval = timedelta(seconds=SCAN_INTERVAL) return self.device - - -class TradfriGroupDataUpdateCoordinator(DataUpdateCoordinator[Group]): - """Coordinator to manage data for a specific Tradfri group.""" - - def __init__( - self, - hass: HomeAssistant, - *, - api: Callable[[Command | list[Command]], Any], - group: Group, - ) -> None: - """Initialize group coordinator.""" - self.api = api - self.group = group - self._exception: Exception | None = None - - super().__init__( - hass, - _LOGGER, - name=f"Update coordinator for {group}", - update_interval=timedelta(seconds=SCAN_INTERVAL), - ) - - async def set_hub_available(self, available: bool) -> None: - """Set status of hub.""" - if available != self.last_update_success: - if not available: - self.last_update_success = False - await self.async_request_refresh() - - async def _async_update_data(self) -> Group: - """Fetch data from the gateway for a specific group.""" - self.update_interval = timedelta(seconds=SCAN_INTERVAL) # Reset update interval - cmd = self.group.update() - try: - await self.api(cmd) - except RequestError as exc: - self.update_interval = timedelta( - seconds=5 - ) # Change interval so we get a swift refresh - raise UpdateFailed("Unable to update group coordinator") from exc - - return self.group diff --git a/homeassistant/components/tradfri/diagnostics.py b/homeassistant/components/tradfri/diagnostics.py index 81f20c4a46a..c415632faa4 100644 --- a/homeassistant/components/tradfri/diagnostics.py +++ b/homeassistant/components/tradfri/diagnostics.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, GROUPS_LIST +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN async def async_get_config_entry_diagnostics( @@ -32,5 +32,4 @@ async def async_get_config_entry_diagnostics( return { "gateway_version": device.sw_version, "device_data": sorted(device_data), - "no_of_groups": len(coordinator_data[GROUPS_LIST]), } diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index bae626017e7..506343d3d4d 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -5,7 +5,6 @@ from collections.abc import Callable from typing import Any, cast from pytradfri.command import Command -from pytradfri.group import Group from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -20,7 +19,6 @@ from homeassistant.components.light import ( from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity import homeassistant.util.color as color_util from .base_class import TradfriBaseEntity @@ -30,19 +28,13 @@ from .const import ( ATTR_SAT, ATTR_TRANSITION_TIME, CONF_GATEWAY_ID, - CONF_IMPORT_GROUPS, COORDINATOR, COORDINATOR_LIST, DOMAIN, - GROUPS_LIST, KEY_API, - SUPPORTED_GROUP_FEATURES, SUPPORTED_LIGHT_FEATURES, ) -from .coordinator import ( - TradfriDeviceDataUpdateCoordinator, - TradfriGroupDataUpdateCoordinator, -) +from .coordinator import TradfriDeviceDataUpdateCoordinator async def async_setup_entry( @@ -55,7 +47,7 @@ async def async_setup_entry( coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] api = coordinator_data[KEY_API] - entities: list = [ + async_add_entities( TradfriLight( device_coordinator, api, @@ -63,71 +55,7 @@ async def async_setup_entry( ) for device_coordinator in coordinator_data[COORDINATOR_LIST] if device_coordinator.device.has_light_control - ] - - if config_entry.data[CONF_IMPORT_GROUPS] and ( - group_coordinators := coordinator_data[GROUPS_LIST] - ): - entities.extend( - [ - TradfriGroup(group_coordinator, api, gateway_id) - for group_coordinator in group_coordinators - ] - ) - - async_add_entities(entities) - - -class TradfriGroup(CoordinatorEntity[TradfriGroupDataUpdateCoordinator], LightEntity): - """The platform class for light groups required by hass.""" - - _attr_supported_features = SUPPORTED_GROUP_FEATURES - - def __init__( - self, - group_coordinator: TradfriGroupDataUpdateCoordinator, - api: Callable[[Command | list[Command]], Any], - gateway_id: str, - ) -> None: - """Initialize a Group.""" - super().__init__(coordinator=group_coordinator) - - self._group: Group = self.coordinator.data - - self._api = api - self._attr_unique_id = f"group-{gateway_id}-{self._group.id}" - - @property - def is_on(self) -> bool: - """Return true if group lights are on.""" - return cast(bool, self._group.state) - - @property - def brightness(self) -> int | None: - """Return the brightness of the group lights.""" - return cast(int, self._group.dimmer) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Instruct the group lights to turn off.""" - await self._api(self._group.set_state(0)) - - await self.coordinator.async_request_refresh() - - async def async_turn_on(self, **kwargs: Any) -> None: - """Instruct the group lights to turn on, or dim.""" - keys = {} - if ATTR_TRANSITION in kwargs: - keys["transition_time"] = int(kwargs[ATTR_TRANSITION]) * 10 - - if ATTR_BRIGHTNESS in kwargs: - if kwargs[ATTR_BRIGHTNESS] == 255: - kwargs[ATTR_BRIGHTNESS] = 254 - - await self._api(self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)) - else: - await self._api(self._group.set_state(1)) - - await self.coordinator.async_request_refresh() + ) class TradfriLight(TradfriBaseEntity, LightEntity): diff --git a/tests/components/tradfri/common.py b/tests/components/tradfri/common.py index feeb60ab7c9..81e21524eb0 100644 --- a/tests/components/tradfri/common.py +++ b/tests/components/tradfri/common.py @@ -14,7 +14,6 @@ async def setup_integration(hass): "host": "mock-host", "identity": "mock-identity", "key": "mock-key", - "import_groups": True, "gateway_id": GATEWAY_ID, }, ) diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 90fce929f58..17b5530fe81 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -130,126 +130,6 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup): } -async def test_import_connection(hass, mock_auth, mock_entry_setup): - """Test a connection via import.""" - mock_auth.side_effect = lambda hass, host, code: { - "host": host, - "gateway_id": "bla", - "identity": "mock-iden", - "key": "mock-key", - } - - flow = await hass.config_entries.flow.async_init( - "tradfri", - context={"source": config_entries.SOURCE_IMPORT}, - data={"host": "123.123.123.123", "import_groups": True}, - ) - - result = await hass.config_entries.flow.async_configure( - flow["flow_id"], {"security_code": "abcd"} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].data == { - "host": "123.123.123.123", - "gateway_id": "bla", - "identity": "mock-iden", - "key": "mock-key", - "import_groups": True, - } - - assert len(mock_entry_setup.mock_calls) == 1 - - -async def test_import_connection_no_groups(hass, mock_auth, mock_entry_setup): - """Test a connection via import and no groups allowed.""" - mock_auth.side_effect = lambda hass, host, code: { - "host": host, - "gateway_id": "bla", - "identity": "mock-iden", - "key": "mock-key", - } - - flow = await hass.config_entries.flow.async_init( - "tradfri", - context={"source": config_entries.SOURCE_IMPORT}, - data={"host": "123.123.123.123", "import_groups": False}, - ) - - result = await hass.config_entries.flow.async_configure( - flow["flow_id"], {"security_code": "abcd"} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].data == { - "host": "123.123.123.123", - "gateway_id": "bla", - "identity": "mock-iden", - "key": "mock-key", - "import_groups": False, - } - - assert len(mock_entry_setup.mock_calls) == 1 - - -async def test_import_connection_legacy(hass, mock_gateway_info, mock_entry_setup): - """Test a connection via import.""" - mock_gateway_info.side_effect = lambda hass, host, identity, key: { - "host": host, - "identity": identity, - "key": key, - "gateway_id": "mock-gateway", - } - - result = await hass.config_entries.flow.async_init( - "tradfri", - context={"source": config_entries.SOURCE_IMPORT}, - data={"host": "123.123.123.123", "key": "mock-key", "import_groups": True}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].data == { - "host": "123.123.123.123", - "gateway_id": "mock-gateway", - "identity": "homeassistant", - "key": "mock-key", - "import_groups": True, - } - - assert len(mock_gateway_info.mock_calls) == 1 - assert len(mock_entry_setup.mock_calls) == 1 - - -async def test_import_connection_legacy_no_groups( - hass, mock_gateway_info, mock_entry_setup -): - """Test a connection via legacy import and no groups allowed.""" - mock_gateway_info.side_effect = lambda hass, host, identity, key: { - "host": host, - "identity": identity, - "key": key, - "gateway_id": "mock-gateway", - } - - result = await hass.config_entries.flow.async_init( - "tradfri", - context={"source": config_entries.SOURCE_IMPORT}, - data={"host": "123.123.123.123", "key": "mock-key", "import_groups": False}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].data == { - "host": "123.123.123.123", - "gateway_id": "mock-gateway", - "identity": "homeassistant", - "key": "mock-key", - "import_groups": False, - } - - assert len(mock_gateway_info.mock_calls) == 1 - assert len(mock_entry_setup.mock_calls) == 1 - - async def test_discovery_duplicate_aborted(hass): """Test a duplicate discovery host aborts and updates existing entry.""" entry = MockConfigEntry( diff --git a/tests/components/tradfri/test_diagnostics.py b/tests/components/tradfri/test_diagnostics.py index d76e80a8b9c..c950f4cba9f 100644 --- a/tests/components/tradfri/test_diagnostics.py +++ b/tests/components/tradfri/test_diagnostics.py @@ -7,7 +7,6 @@ from homeassistant.core import HomeAssistant from .common import setup_integration from .test_fan import mock_fan -from .test_light import mock_group from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -30,11 +29,6 @@ async def test_diagnostics( ) ) - mock_gateway.mock_groups.append( - # Add a group - mock_group(test_state={"state": True, "dimmer": 100}) - ) - init_integration = await setup_integration(hass) result = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) @@ -42,4 +36,3 @@ async def test_diagnostics( assert isinstance(result, dict) assert result["gateway_version"] == "1.2.1234" assert len(result["device_data"]) == 1 - assert result["no_of_groups"] == 1 diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index 2a26391c43f..9d8cf60b2bf 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -17,7 +17,6 @@ async def test_entry_setup_unload(hass, mock_api_factory): tradfri.CONF_HOST: "mock-host", tradfri.CONF_IDENTITY: "mock-identity", tradfri.CONF_KEY: "mock-key", - tradfri.CONF_IMPORT_GROUPS: True, tradfri.CONF_GATEWAY_ID: GATEWAY_ID, }, ) @@ -59,7 +58,6 @@ async def test_remove_stale_devices(hass, mock_api_factory): tradfri.CONF_HOST: "mock-host", tradfri.CONF_IDENTITY: "mock-identity", tradfri.CONF_KEY: "mock-key", - tradfri.CONF_IMPORT_GROUPS: True, tradfri.CONF_GATEWAY_ID: GATEWAY_ID, }, ) diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index 1ed24d7b080..8e51e37576e 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -305,92 +305,3 @@ async def test_turn_off(hass, mock_gateway, mock_api_factory): # Check that the state is correct. states = hass.states.get("light.tradfri_light_0") assert states.state == "off" - - -def mock_group(test_state=None, group_number=0): - """Mock a Tradfri group.""" - if test_state is None: - test_state = {} - default_state = {"state": False, "dimmer": 0} - - state = {**default_state, **test_state} - - _mock_group = Mock(member_ids=[], observe=Mock(), **state) - _mock_group.name = f"tradfri_group_{group_number}" - _mock_group.id = group_number - return _mock_group - - -async def test_group(hass, mock_gateway, mock_api_factory): - """Test that groups are correctly added.""" - mock_gateway.mock_groups.append(mock_group()) - state = {"state": True, "dimmer": 100} - mock_gateway.mock_groups.append(mock_group(state, 1)) - await setup_integration(hass) - - group = hass.states.get("light.tradfri_group_mock_gateway_id_0") - assert group is not None - assert group.state == "off" - - group = hass.states.get("light.tradfri_group_mock_gateway_id_1") - assert group is not None - assert group.state == "on" - assert group.attributes["brightness"] == 100 - - -async def test_group_turn_on(hass, mock_gateway, mock_api_factory): - """Test turning on a group.""" - group = mock_group() - group2 = mock_group(group_number=1) - group3 = mock_group(group_number=2) - mock_gateway.mock_groups.append(group) - mock_gateway.mock_groups.append(group2) - mock_gateway.mock_groups.append(group3) - await setup_integration(hass) - - # Use the turn_off service call to change the light state. - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": "light.tradfri_group_mock_gateway_id_0"}, - blocking=True, - ) - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": "light.tradfri_group_mock_gateway_id_1", "brightness": 100}, - blocking=True, - ) - await hass.services.async_call( - "light", - "turn_on", - { - "entity_id": "light.tradfri_group_mock_gateway_id_2", - "brightness": 100, - "transition": 1, - }, - blocking=True, - ) - await hass.async_block_till_done() - - group.set_state.assert_called_with(1) - group2.set_dimmer.assert_called_with(100) - group3.set_dimmer.assert_called_with(100, transition_time=10) - - -async def test_group_turn_off(hass, mock_gateway, mock_api_factory): - """Test turning off a group.""" - group = mock_group({"state": True}) - mock_gateway.mock_groups.append(group) - await setup_integration(hass) - - # Use the turn_off service call to change the light state. - await hass.services.async_call( - "light", - "turn_off", - {"entity_id": "light.tradfri_group_mock_gateway_id_0"}, - blocking=True, - ) - await hass.async_block_till_done() - - group.set_state.assert_called_with(0)