From 276e2e8f59f01a088f0b75f6f8147225c75b5c0b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 21 Mar 2025 18:32:05 +0100 Subject: [PATCH] Move device creation in SmartThings (#141074) Move device creation --- .../components/smartthings/__init__.py | 81 ++++++++++++++----- .../components/smartthings/binary_sensor.py | 4 +- .../components/smartthings/climate.py | 14 +--- homeassistant/components/smartthings/cover.py | 6 +- .../components/smartthings/entity.py | 31 ------- homeassistant/components/smartthings/event.py | 7 +- homeassistant/components/smartthings/fan.py | 7 +- homeassistant/components/smartthings/light.py | 7 +- homeassistant/components/smartthings/lock.py | 2 +- .../components/smartthings/sensor.py | 4 +- .../components/smartthings/switch.py | 4 +- .../components/smartthings/update.py | 4 +- homeassistant/components/smartthings/valve.py | 8 +- .../smartthings/snapshots/test_init.ambr | 2 +- 14 files changed, 81 insertions(+), 100 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index c90dccfe937..5cc7b3e2c36 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass import logging -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast from aiohttp import ClientError from pysmartthings import ( @@ -22,6 +22,12 @@ from pysmartthings import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_CONNECTIONS, + ATTR_HW_VERSION, + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTR_SW_VERSION, + ATTR_VIA_DEVICE, CONF_ACCESS_TOKEN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP, @@ -172,25 +178,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry) raise ConfigEntryAuthFailed from err device_registry = dr.async_get(hass) - for dev in device_status.values(): - for component in dev.device.components: - if component.id == MAIN and Capability.BRIDGE in component.capabilities: - assert dev.device.hub - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, dev.device.device_id)}, - connections=( - {(dr.CONNECTION_NETWORK_MAC, dev.device.hub.mac_address)} - if dev.device.hub.mac_address - else set() - ), - name=dev.device.label, - sw_version=dev.device.hub.firmware_version, - model=dev.device.hub.hardware_type, - suggested_area=( - rooms.get(dev.device.room_id) if dev.device.room_id else None - ), - ) + create_devices(device_registry, device_status, entry, rooms) + scenes = { scene.scene_id: scene for scene in await client.get_scenes(location_id=entry.data[CONF_LOCATION_ID]) @@ -278,6 +267,58 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True +def create_devices( + device_registry: dr.DeviceRegistry, + devices: dict[str, FullDevice], + entry: SmartThingsConfigEntry, + rooms: dict[str, str], +) -> None: + """Create devices in the device registry.""" + for device in devices.values(): + kwargs: dict[str, Any] = {} + if device.device.hub is not None: + kwargs = { + ATTR_SW_VERSION: device.device.hub.firmware_version, + ATTR_MODEL: device.device.hub.hardware_type, + } + if device.device.hub.mac_address: + kwargs[ATTR_CONNECTIONS] = { + (dr.CONNECTION_NETWORK_MAC, device.device.hub.mac_address) + } + if device.device.parent_device_id: + kwargs[ATTR_VIA_DEVICE] = (DOMAIN, device.device.parent_device_id) + if (ocf := device.device.ocf) is not None: + kwargs.update( + { + ATTR_MANUFACTURER: ocf.manufacturer_name, + ATTR_MODEL: ( + (ocf.model_number.split("|")[0]) if ocf.model_number else None + ), + ATTR_HW_VERSION: ocf.hardware_version, + ATTR_SW_VERSION: ocf.firmware_version, + } + ) + if (viper := device.device.viper) is not None: + kwargs.update( + { + ATTR_MANUFACTURER: viper.manufacturer_name, + ATTR_MODEL: viper.model_name, + ATTR_HW_VERSION: viper.hardware_version, + ATTR_SW_VERSION: viper.software_version, + } + ) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, device.device.device_id)}, + configuration_url="https://account.smartthings.com", + name=device.device.label, + suggested_area=( + rooms.get(device.device.room_id) if device.device.room_id else None + ), + **kwargs, + ) + + KEEP_CAPABILITY_QUIRK: dict[ Capability | str, Callable[[dict[Attribute | str, Status]], bool] ] = { diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index ace23ba4ec2..b67b15dfdbc 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -134,7 +134,6 @@ async def async_setup_entry( entry_data.client, device, description, - entry_data.rooms, capability, attribute, ) @@ -155,12 +154,11 @@ class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity): client: SmartThings, device: FullDevice, entity_description: SmartThingsBinarySensorEntityDescription, - rooms: dict[str, str], capability: Capability, attribute: Attribute, ) -> None: """Init the class.""" - super().__init__(client, device, rooms, {capability}) + super().__init__(client, device, {capability}) self._attribute = attribute self.capability = capability self.entity_description = entity_description diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index c6dee3e2be4..e20f191352f 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -118,12 +118,12 @@ async def async_setup_entry( """Add climate entities for a config entry.""" entry_data = entry.runtime_data entities: list[ClimateEntity] = [ - SmartThingsAirConditioner(entry_data.client, entry_data.rooms, device) + SmartThingsAirConditioner(entry_data.client, device) for device in entry_data.devices.values() if all(capability in device.status[MAIN] for capability in AC_CAPABILITIES) ] entities.extend( - SmartThingsThermostat(entry_data.client, entry_data.rooms, device) + SmartThingsThermostat(entry_data.client, device) for device in entry_data.devices.values() if all( capability in device.status[MAIN] for capability in THERMOSTAT_CAPABILITIES @@ -137,14 +137,11 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): _attr_name = None - def __init__( - self, client: SmartThings, rooms: dict[str, str], device: FullDevice - ) -> None: + def __init__(self, client: SmartThings, device: FullDevice) -> None: """Init the class.""" super().__init__( client, device, - rooms, { Capability.THERMOSTAT_FAN_MODE, Capability.THERMOSTAT_MODE, @@ -338,14 +335,11 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): _attr_name = None _attr_preset_mode = None - def __init__( - self, client: SmartThings, rooms: dict[str, str], device: FullDevice - ) -> None: + def __init__(self, client: SmartThings, device: FullDevice) -> None: """Init the class.""" super().__init__( client, device, - rooms, { Capability.AIR_CONDITIONER_MODE, Capability.SWITCH, diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 84bf0412ab4..0b68409443d 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -41,9 +41,7 @@ async def async_setup_entry( """Add covers for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsCover( - entry_data.client, device, entry_data.rooms, Capability(capability) - ) + SmartThingsCover(entry_data.client, device, Capability(capability)) for device in entry_data.devices.values() for capability in device.status[MAIN] if capability in CAPABILITIES @@ -60,14 +58,12 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): self, client: SmartThings, device: FullDevice, - rooms: dict[str, str], capability: Capability, ) -> None: """Initialize the cover class.""" super().__init__( client, device, - rooms, { capability, Capability.BATTERY, diff --git a/homeassistant/components/smartthings/entity.py b/homeassistant/components/smartthings/entity.py index 660ab499d19..12c07bea983 100644 --- a/homeassistant/components/smartthings/entity.py +++ b/homeassistant/components/smartthings/entity.py @@ -30,7 +30,6 @@ class SmartThingsEntity(Entity): self, client: SmartThings, device: FullDevice, - rooms: dict[str, str], capabilities: set[Capability], *, component: str = MAIN, @@ -47,38 +46,8 @@ class SmartThingsEntity(Entity): self.device = device self._attr_unique_id = device.device.device_id self._attr_device_info = DeviceInfo( - configuration_url="https://account.smartthings.com", identifiers={(DOMAIN, device.device.device_id)}, - name=device.device.label, - suggested_area=( - rooms.get(device.device.room_id) if device.device.room_id else None - ), ) - if device.device.parent_device_id: - self._attr_device_info["via_device"] = ( - DOMAIN, - device.device.parent_device_id, - ) - if (ocf := device.device.ocf) is not None: - self._attr_device_info.update( - { - "manufacturer": ocf.manufacturer_name, - "model": ( - (ocf.model_number.split("|")[0]) if ocf.model_number else None - ), - "hw_version": ocf.hardware_version, - "sw_version": ocf.firmware_version, - } - ) - if (viper := device.device.viper) is not None: - self._attr_device_info.update( - { - "manufacturer": viper.manufacturer_name, - "model": viper.model_name, - "hw_version": viper.hardware_version, - "sw_version": viper.software_version, - } - ) async def async_added_to_hass(self) -> None: """Subscribe to updates.""" diff --git a/homeassistant/components/smartthings/event.py b/homeassistant/components/smartthings/event.py index b629bd92b35..e22a32c7726 100644 --- a/homeassistant/components/smartthings/event.py +++ b/homeassistant/components/smartthings/event.py @@ -22,7 +22,7 @@ async def async_setup_entry( """Add events for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsButtonEvent(entry_data.client, entry_data.rooms, device, component) + SmartThingsButtonEvent(entry_data.client, device, component) for device in entry_data.devices.values() for component in device.device.components if Capability.BUTTON in component.capabilities @@ -38,14 +38,11 @@ class SmartThingsButtonEvent(SmartThingsEntity, EventEntity): def __init__( self, client: SmartThings, - rooms: dict[str, str], device: FullDevice, component: Component, ) -> None: """Init the class.""" - super().__init__( - client, device, rooms, {Capability.BUTTON}, component=component.id - ) + super().__init__(client, device, {Capability.BUTTON}, component=component.id) self._attr_name = component.label self._attr_unique_id = ( f"{device.device.device_id}_{component.id}_{Capability.BUTTON}" diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index ef3d9702ce2..1c4cb4edc4a 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -31,7 +31,7 @@ async def async_setup_entry( """Add fans for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsFan(entry_data.client, entry_data.rooms, device) + SmartThingsFan(entry_data.client, device) for device in entry_data.devices.values() if Capability.SWITCH in device.status[MAIN] and any( @@ -51,14 +51,11 @@ class SmartThingsFan(SmartThingsEntity, FanEntity): _attr_name = None _attr_speed_count = int_states_in_range(SPEED_RANGE) - def __init__( - self, client: SmartThings, rooms: dict[str, str], device: FullDevice - ) -> None: + def __init__(self, client: SmartThings, device: FullDevice) -> None: """Init the class.""" super().__init__( client, device, - rooms, { Capability.SWITCH, Capability.FAN_SPEED, diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 12c7f7ebbcb..1ad315bcd97 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -41,7 +41,7 @@ async def async_setup_entry( """Add lights for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsLight(entry_data.client, entry_data.rooms, device) + SmartThingsLight(entry_data.client, device) for device in entry_data.devices.values() if Capability.SWITCH in device.status[MAIN] and any(capability in device.status[MAIN] for capability in CAPABILITIES) @@ -71,14 +71,11 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity): # highest kelvin found supported across 20+ handlers. _attr_max_color_temp_kelvin = 9000 # 111 mireds - def __init__( - self, client: SmartThings, rooms: dict[str, str], device: FullDevice - ) -> None: + def __init__(self, client: SmartThings, device: FullDevice) -> None: """Initialize a SmartThingsLight.""" super().__init__( client, device, - rooms, { Capability.COLOR_CONTROL, Capability.COLOR_TEMPERATURE, diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index 76a643e417e..f56ecd5d565 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -33,7 +33,7 @@ async def async_setup_entry( """Add locks for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsLock(entry_data.client, device, entry_data.rooms, {Capability.LOCK}) + SmartThingsLock(entry_data.client, device, {Capability.LOCK}) for device in entry_data.devices.values() if Capability.LOCK in device.status[MAIN] ) diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 21d256968ae..ee8550e4f06 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -997,7 +997,6 @@ async def async_setup_entry( entry_data.client, device, description, - entry_data.rooms, capability, attribute, ) @@ -1030,7 +1029,6 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity): client: SmartThings, device: FullDevice, entity_description: SmartThingsSensorEntityDescription, - rooms: dict[str, str], capability: Capability, attribute: Attribute, ) -> None: @@ -1038,7 +1036,7 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity): capabilities_to_subscribe = {capability} if entity_description.use_temperature_unit: capabilities_to_subscribe.add(Capability.TEMPERATURE_MEASUREMENT) - super().__init__(client, device, rooms, capabilities_to_subscribe) + super().__init__(client, device, capabilities_to_subscribe) self._attr_unique_id = f"{device.device.device_id}{entity_description.unique_id_separator}{entity_description.key}" self._attribute = attribute self.capability = capability diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index f470a90bb39..380005f1b93 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -37,9 +37,7 @@ async def async_setup_entry( """Add switches for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsSwitch( - entry_data.client, device, entry_data.rooms, {Capability.SWITCH} - ) + SmartThingsSwitch(entry_data.client, device, {Capability.SWITCH}) for device in entry_data.devices.values() if Capability.SWITCH in device.status[MAIN] and not any(capability in device.status[MAIN] for capability in CAPABILITIES) diff --git a/homeassistant/components/smartthings/update.py b/homeassistant/components/smartthings/update.py index bd856bd38ba..bb226918596 100644 --- a/homeassistant/components/smartthings/update.py +++ b/homeassistant/components/smartthings/update.py @@ -28,9 +28,7 @@ async def async_setup_entry( """Add update entities for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsUpdateEntity( - entry_data.client, device, entry_data.rooms, {Capability.FIRMWARE_UPDATE} - ) + SmartThingsUpdateEntity(entry_data.client, device, {Capability.FIRMWARE_UPDATE}) for device in entry_data.devices.values() if Capability.FIRMWARE_UPDATE in device.status[MAIN] ) diff --git a/homeassistant/components/smartthings/valve.py b/homeassistant/components/smartthings/valve.py index a38eb9e65c4..3c401c087ec 100644 --- a/homeassistant/components/smartthings/valve.py +++ b/homeassistant/components/smartthings/valve.py @@ -30,7 +30,7 @@ async def async_setup_entry( """Add valves for a config entry.""" entry_data = entry.runtime_data async_add_entities( - SmartThingsValve(entry_data.client, entry_data.rooms, device) + SmartThingsValve(entry_data.client, device) for device in entry_data.devices.values() if Capability.VALVE in device.status[MAIN] ) @@ -43,11 +43,9 @@ class SmartThingsValve(SmartThingsEntity, ValveEntity): _attr_reports_position = False _attr_name = None - def __init__( - self, client: SmartThings, rooms: dict[str, str], device: FullDevice - ) -> None: + def __init__(self, client: SmartThings, device: FullDevice) -> None: """Init the class.""" - super().__init__(client, device, rooms, {Capability.VALVE}) + super().__init__(client, device, {Capability.VALVE}) self._attr_device_class = DEVICE_CLASS_MAP.get( device.device.components[0].user_category or device.device.components[0].manufacturer_category diff --git a/tests/components/smartthings/snapshots/test_init.ambr b/tests/components/smartthings/snapshots/test_init.ambr index 930b3851806..d6e98553015 100644 --- a/tests/components/smartthings/snapshots/test_init.ambr +++ b/tests/components/smartthings/snapshots/test_init.ambr @@ -1624,7 +1624,7 @@ 'area_id': 'theater', 'config_entries': , 'config_entries_subentries': , - 'configuration_url': None, + 'configuration_url': 'https://account.smartthings.com', 'connections': set({ tuple( 'mac',