Drop support for Tradfri groups and YAML configuration (#68033)

* Drop support for Tradfri groups

* Remove context

* Remove async_setup

* Mark removed

* Pass generator expression
This commit is contained in:
Patrik Lindgren 2022-04-01 23:26:35 +02:00 committed by GitHub
parent 853923c30a
commit 4b5996c5ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 8 additions and 431 deletions

View file

@ -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:

View file

@ -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"

View file

@ -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

View file

@ -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]),
}

View file

@ -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):

View file

@ -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,
},
)

View file

@ -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(

View file

@ -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

View file

@ -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,
},
)

View file

@ -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)