Move device creation in SmartThings (#141074)

Move device creation
This commit is contained in:
Joost Lekkerkerker 2025-03-21 18:32:05 +01:00 committed by GitHub
parent 5f67623214
commit 276e2e8f59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 81 additions and 100 deletions

View File

@ -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]
] = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1624,7 +1624,7 @@
'area_id': 'theater',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': None,
'configuration_url': 'https://account.smartthings.com',
'connections': set({
tuple(
'mac',