mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Adjust Hue integration to use Entity descriptions and translatable entity names (#101413)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
8a83e810b8
commit
6393171fa4
@ -71,6 +71,7 @@ class HueButtonEventEntity(HueBaseEntity, EventEntity):
|
|||||||
key="button",
|
key="button",
|
||||||
device_class=EventDeviceClass.BUTTON,
|
device_class=EventDeviceClass.BUTTON,
|
||||||
translation_key="button",
|
translation_key="button",
|
||||||
|
has_entity_name=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
@ -89,7 +90,8 @@ class HueButtonEventEntity(HueBaseEntity, EventEntity):
|
|||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return name for the entity."""
|
"""Return name for the entity."""
|
||||||
return f"{super().name} {self.resource.metadata.control_id}"
|
# this can be translated too as soon as we support arguments into translations ?
|
||||||
|
return f"Button {self.resource.metadata.control_id}"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_event(self, event_type: EventType, resource: Button) -> None:
|
def _handle_event(self, event_type: EventType, resource: Button) -> None:
|
||||||
@ -112,6 +114,7 @@ class HueRotaryEventEntity(HueBaseEntity, EventEntity):
|
|||||||
RelativeRotaryDirection.CLOCK_WISE.value,
|
RelativeRotaryDirection.CLOCK_WISE.value,
|
||||||
RelativeRotaryDirection.COUNTER_CLOCK_WISE.value,
|
RelativeRotaryDirection.COUNTER_CLOCK_WISE.value,
|
||||||
],
|
],
|
||||||
|
has_entity_name=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -13,7 +13,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity
|
from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
async_get_current_platform,
|
async_get_current_platform,
|
||||||
@ -86,6 +86,8 @@ async def async_setup_entry(
|
|||||||
class HueSceneEntityBase(HueBaseEntity, SceneEntity):
|
class HueSceneEntityBase(HueBaseEntity, SceneEntity):
|
||||||
"""Base Representation of a Scene entity from Hue Scenes."""
|
"""Base Representation of a Scene entity from Hue Scenes."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bridge: HueBridge,
|
bridge: HueBridge,
|
||||||
@ -97,6 +99,11 @@ class HueSceneEntityBase(HueBaseEntity, SceneEntity):
|
|||||||
self.resource = resource
|
self.resource = resource
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.group = self.controller.get_group(self.resource.id)
|
self.group = self.controller.get_group(self.resource.id)
|
||||||
|
# we create a virtual service/device for Hue zones/rooms
|
||||||
|
# so we have a parent for grouped lights and scenes
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self.group.id)},
|
||||||
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call when entity is added."""
|
"""Call when entity is added."""
|
||||||
@ -112,24 +119,8 @@ class HueSceneEntityBase(HueBaseEntity, SceneEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return default entity name."""
|
"""Return name of the scene."""
|
||||||
return f"{self.group.metadata.name} {self.resource.metadata.name}"
|
return self.resource.metadata.name
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return device (service) info."""
|
|
||||||
# we create a virtual service/device for Hue scenes
|
|
||||||
# so we have a parent for grouped lights and scenes
|
|
||||||
group_type = self.group.type.value.title()
|
|
||||||
return DeviceInfo(
|
|
||||||
identifiers={(DOMAIN, self.group.id)},
|
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
name=self.group.metadata.name,
|
|
||||||
manufacturer=self.bridge.api.config.bridge_device.product_data.manufacturer_name,
|
|
||||||
model=self.group.type.value.title(),
|
|
||||||
suggested_area=self.group.metadata.name if group_type == "Room" else None,
|
|
||||||
via_device=(DOMAIN, self.bridge.api.config.bridge_device.id),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HueSceneEntity(HueSceneEntityBase):
|
class HueSceneEntity(HueSceneEntityBase):
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"zigbee_connectivity": {
|
"zigbee_connectivity": {
|
||||||
|
"name": "Zigbee connectivity",
|
||||||
"state": {
|
"state": {
|
||||||
"connected": "[%key:common::state::connected%]",
|
"connected": "[%key:common::state::connected%]",
|
||||||
"disconnected": "[%key:common::state::disconnected%]",
|
"disconnected": "[%key:common::state::disconnected%]",
|
||||||
@ -106,11 +107,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"automation": {
|
"motion_sensor_enabled": {
|
||||||
"state": {
|
"name": "Motion sensor enabled"
|
||||||
"on": "[%key:common::state::enabled%]",
|
},
|
||||||
"off": "[%key:common::state::disabled%]"
|
"light_sensor_enabled": {
|
||||||
}
|
"name": "Light sensor enabled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Support for switch platform for Hue resources (V2 only)."""
|
"""Support for switch platform for Hue resources (V2 only)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, TypeAlias
|
from typing import Any
|
||||||
|
|
||||||
from aiohue.v2 import HueBridgeV2
|
from aiohue.v2 import HueBridgeV2
|
||||||
from aiohue.v2.controllers.config import BehaviorInstance, BehaviorInstanceController
|
from aiohue.v2.controllers.config import BehaviorInstance, BehaviorInstanceController
|
||||||
@ -27,12 +27,6 @@ from .bridge import HueBridge
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .v2.entity import HueBaseEntity
|
from .v2.entity import HueBaseEntity
|
||||||
|
|
||||||
ControllerType: TypeAlias = (
|
|
||||||
BehaviorInstanceController | LightLevelController | MotionController
|
|
||||||
)
|
|
||||||
|
|
||||||
SensingService: TypeAlias = LightLevel | Motion
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -48,20 +42,22 @@ async def async_setup_entry(
|
|||||||
raise NotImplementedError("Switch support is only available for V2 bridges")
|
raise NotImplementedError("Switch support is only available for V2 bridges")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def register_items(controller: ControllerType):
|
def register_items(
|
||||||
|
controller: BehaviorInstanceController
|
||||||
|
| LightLevelController
|
||||||
|
| MotionController,
|
||||||
|
switch_class: type[
|
||||||
|
HueBehaviorInstanceEnabledEntity
|
||||||
|
| HueLightSensorEnabledEntity
|
||||||
|
| HueMotionSensorEnabledEntity
|
||||||
|
],
|
||||||
|
):
|
||||||
@callback
|
@callback
|
||||||
def async_add_entity(
|
def async_add_entity(
|
||||||
event_type: EventType, resource: SensingService | BehaviorInstance
|
event_type: EventType, resource: BehaviorInstance | LightLevel | Motion
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add entity from Hue resource."""
|
"""Add entity from Hue resource."""
|
||||||
if isinstance(resource, BehaviorInstance):
|
async_add_entities([switch_class(bridge, api.sensors.motion, resource)])
|
||||||
async_add_entities(
|
|
||||||
[HueBehaviorInstanceEnabledEntity(bridge, controller, resource)]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
async_add_entities(
|
|
||||||
[HueSensingServiceEnabledEntity(bridge, controller, resource)]
|
|
||||||
)
|
|
||||||
|
|
||||||
# add all current items in controller
|
# add all current items in controller
|
||||||
for item in controller:
|
for item in controller:
|
||||||
@ -75,15 +71,23 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# setup for each switch-type hue resource
|
# setup for each switch-type hue resource
|
||||||
register_items(api.sensors.motion)
|
register_items(api.sensors.motion, HueMotionSensorEnabledEntity)
|
||||||
register_items(api.sensors.light_level)
|
register_items(api.sensors.light_level, HueLightSensorEnabledEntity)
|
||||||
register_items(api.config.behavior_instance)
|
register_items(api.config.behavior_instance, HueBehaviorInstanceEnabledEntity)
|
||||||
|
|
||||||
|
|
||||||
class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
|
class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
|
||||||
"""Representation of a Switch entity from a Hue resource that can be toggled enabled."""
|
"""Representation of a Switch entity from a Hue resource that can be toggled enabled."""
|
||||||
|
|
||||||
controller: BehaviorInstanceController | LightLevelController | MotionController
|
controller: BehaviorInstanceController | LightLevelController | MotionController
|
||||||
|
resource: BehaviorInstance | LightLevel | Motion
|
||||||
|
|
||||||
|
entity_description = SwitchEntityDescription(
|
||||||
|
key="sensing_service_enabled",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
has_entity_name=True,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
@ -103,16 +107,6 @@ class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HueSensingServiceEnabledEntity(HueResourceEnabledEntity):
|
|
||||||
"""Representation of a Switch entity from Hue SensingService."""
|
|
||||||
|
|
||||||
entity_description = SwitchEntityDescription(
|
|
||||||
key="behavior_instance",
|
|
||||||
device_class=SwitchDeviceClass.SWITCH,
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HueBehaviorInstanceEnabledEntity(HueResourceEnabledEntity):
|
class HueBehaviorInstanceEnabledEntity(HueResourceEnabledEntity):
|
||||||
"""Representation of a Switch entity to enable/disable a Hue Behavior Instance."""
|
"""Representation of a Switch entity to enable/disable a Hue Behavior Instance."""
|
||||||
|
|
||||||
@ -123,10 +117,33 @@ class HueBehaviorInstanceEnabledEntity(HueResourceEnabledEntity):
|
|||||||
device_class=SwitchDeviceClass.SWITCH,
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
has_entity_name=False,
|
has_entity_name=False,
|
||||||
translation_key="automation",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return name for this entity."""
|
"""Return name for this entity."""
|
||||||
return f"Automation: {self.resource.metadata.name}"
|
return f"Automation: {self.resource.metadata.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class HueMotionSensorEnabledEntity(HueResourceEnabledEntity):
|
||||||
|
"""Representation of a Switch entity to enable/disable a Hue motion sensor."""
|
||||||
|
|
||||||
|
entity_description = SwitchEntityDescription(
|
||||||
|
key="motion_sensor_enabled",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
has_entity_name=True,
|
||||||
|
translation_key="motion_sensor_enabled",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HueLightSensorEnabledEntity(HueResourceEnabledEntity):
|
||||||
|
"""Representation of a Switch entity to enable/disable a Hue light sensor."""
|
||||||
|
|
||||||
|
entity_description = SwitchEntityDescription(
|
||||||
|
key="light_sensor_enabled",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
has_entity_name=True,
|
||||||
|
translation_key="light_sensor_enabled",
|
||||||
|
)
|
||||||
|
@ -24,8 +24,10 @@ from aiohue.v2.models.tamper import Tamper, TamperState
|
|||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
|
BinarySensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
@ -80,25 +82,17 @@ async def async_setup_entry(
|
|||||||
register_items(api.sensors.tamper, HueTamperSensor)
|
register_items(api.sensors.tamper, HueTamperSensor)
|
||||||
|
|
||||||
|
|
||||||
class HueBinarySensorBase(HueBaseEntity, BinarySensorEntity):
|
class HueMotionSensor(HueBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a Hue binary_sensor."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
bridge: HueBridge,
|
|
||||||
controller: ControllerType,
|
|
||||||
resource: SensorType,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the binary sensor."""
|
|
||||||
super().__init__(bridge, controller, resource)
|
|
||||||
self.resource = resource
|
|
||||||
self.controller = controller
|
|
||||||
|
|
||||||
|
|
||||||
class HueMotionSensor(HueBinarySensorBase):
|
|
||||||
"""Representation of a Hue Motion sensor."""
|
"""Representation of a Hue Motion sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.MOTION
|
controller: CameraMotionController | MotionController
|
||||||
|
resource: CameraMotion | Motion
|
||||||
|
|
||||||
|
entity_description = BinarySensorEntityDescription(
|
||||||
|
key="motion_sensor",
|
||||||
|
device_class=BinarySensorDeviceClass.MOTION,
|
||||||
|
has_entity_name=True,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
@ -109,10 +103,17 @@ class HueMotionSensor(HueBinarySensorBase):
|
|||||||
return self.resource.motion.value
|
return self.resource.motion.value
|
||||||
|
|
||||||
|
|
||||||
class HueEntertainmentActiveSensor(HueBinarySensorBase):
|
class HueEntertainmentActiveSensor(HueBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a Hue Entertainment Configuration as binary sensor."""
|
"""Representation of a Hue Entertainment Configuration as binary sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.RUNNING
|
controller: EntertainmentConfigurationController
|
||||||
|
resource: EntertainmentConfiguration
|
||||||
|
|
||||||
|
entity_description = BinarySensorEntityDescription(
|
||||||
|
key="entertainment_active_sensor",
|
||||||
|
device_class=BinarySensorDeviceClass.RUNNING,
|
||||||
|
has_entity_name=False,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
@ -122,14 +123,20 @@ class HueEntertainmentActiveSensor(HueBinarySensorBase):
|
|||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return sensor name."""
|
"""Return sensor name."""
|
||||||
type_title = self.resource.type.value.replace("_", " ").title()
|
return self.resource.metadata.name
|
||||||
return f"{self.resource.metadata.name}: {type_title}"
|
|
||||||
|
|
||||||
|
|
||||||
class HueContactSensor(HueBinarySensorBase):
|
class HueContactSensor(HueBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a Hue Contact sensor."""
|
"""Representation of a Hue Contact sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.OPENING
|
controller: ContactController
|
||||||
|
resource: Contact
|
||||||
|
|
||||||
|
entity_description = BinarySensorEntityDescription(
|
||||||
|
key="contact_sensor",
|
||||||
|
device_class=BinarySensorDeviceClass.OPENING,
|
||||||
|
has_entity_name=True,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
@ -140,10 +147,18 @@ class HueContactSensor(HueBinarySensorBase):
|
|||||||
return self.resource.contact_report.state != ContactState.CONTACT
|
return self.resource.contact_report.state != ContactState.CONTACT
|
||||||
|
|
||||||
|
|
||||||
class HueTamperSensor(HueBinarySensorBase):
|
class HueTamperSensor(HueBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a Hue Tamper sensor."""
|
"""Representation of a Hue Tamper sensor."""
|
||||||
|
|
||||||
_attr_device_class = BinarySensorDeviceClass.TAMPER
|
controller: TamperController
|
||||||
|
resource: Tamper
|
||||||
|
|
||||||
|
entity_description = BinarySensorEntityDescription(
|
||||||
|
key="tamper_sensor",
|
||||||
|
device_class=BinarySensorDeviceClass.TAMPER,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
has_entity_name=True,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
|
@ -3,7 +3,9 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from aiohue.v2 import HueBridgeV2
|
from aiohue.v2 import HueBridgeV2
|
||||||
from aiohue.v2.controllers.events import EventType
|
from aiohue.v2.controllers.events import EventType
|
||||||
|
from aiohue.v2.controllers.groups import Room, Zone
|
||||||
from aiohue.v2.models.device import Device, DeviceArchetypes
|
from aiohue.v2.models.device import Device, DeviceArchetypes
|
||||||
|
from aiohue.v2.models.resource import ResourceTypes
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CONNECTIONS,
|
ATTR_CONNECTIONS,
|
||||||
@ -33,23 +35,38 @@ async def async_setup_devices(bridge: "HueBridge"):
|
|||||||
dev_controller = api.devices
|
dev_controller = api.devices
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def add_device(hue_device: Device) -> dr.DeviceEntry:
|
def add_device(hue_resource: Device | Room | Zone) -> dr.DeviceEntry:
|
||||||
"""Register a Hue device in device registry."""
|
"""Register a Hue device in device registry."""
|
||||||
model = f"{hue_device.product_data.product_name} ({hue_device.product_data.model_id})"
|
if isinstance(hue_resource, (Room, Zone)):
|
||||||
|
# Register a Hue Room/Zone as service in HA device registry.
|
||||||
|
return dev_reg.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
entry_type=dr.DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, hue_resource.id)},
|
||||||
|
name=hue_resource.metadata.name,
|
||||||
|
model=hue_resource.type.value.title(),
|
||||||
|
manufacturer=api.config.bridge_device.product_data.manufacturer_name,
|
||||||
|
via_device=(DOMAIN, api.config.bridge_device.id),
|
||||||
|
suggested_area=hue_resource.metadata.name
|
||||||
|
if hue_resource.type == ResourceTypes.ROOM
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
# Register a Hue device resource as device in HA device registry.
|
||||||
|
model = f"{hue_resource.product_data.product_name} ({hue_resource.product_data.model_id})"
|
||||||
params = {
|
params = {
|
||||||
ATTR_IDENTIFIERS: {(DOMAIN, hue_device.id)},
|
ATTR_IDENTIFIERS: {(DOMAIN, hue_resource.id)},
|
||||||
ATTR_SW_VERSION: hue_device.product_data.software_version,
|
ATTR_SW_VERSION: hue_resource.product_data.software_version,
|
||||||
ATTR_NAME: hue_device.metadata.name,
|
ATTR_NAME: hue_resource.metadata.name,
|
||||||
ATTR_MODEL: model,
|
ATTR_MODEL: model,
|
||||||
ATTR_MANUFACTURER: hue_device.product_data.manufacturer_name,
|
ATTR_MANUFACTURER: hue_resource.product_data.manufacturer_name,
|
||||||
}
|
}
|
||||||
if room := dev_controller.get_room(hue_device.id):
|
if room := dev_controller.get_room(hue_resource.id):
|
||||||
params[ATTR_SUGGESTED_AREA] = room.metadata.name
|
params[ATTR_SUGGESTED_AREA] = room.metadata.name
|
||||||
if hue_device.metadata.archetype == DeviceArchetypes.BRIDGE_V2:
|
if hue_resource.metadata.archetype == DeviceArchetypes.BRIDGE_V2:
|
||||||
params[ATTR_IDENTIFIERS].add((DOMAIN, api.config.bridge_id))
|
params[ATTR_IDENTIFIERS].add((DOMAIN, api.config.bridge_id))
|
||||||
else:
|
else:
|
||||||
params[ATTR_VIA_DEVICE] = (DOMAIN, api.config.bridge_device.id)
|
params[ATTR_VIA_DEVICE] = (DOMAIN, api.config.bridge_device.id)
|
||||||
zigbee = dev_controller.get_zigbee_connectivity(hue_device.id)
|
zigbee = dev_controller.get_zigbee_connectivity(hue_resource.id)
|
||||||
if zigbee and zigbee.mac_address:
|
if zigbee and zigbee.mac_address:
|
||||||
params[ATTR_CONNECTIONS] = {(dr.CONNECTION_NETWORK_MAC, zigbee.mac_address)}
|
params[ATTR_CONNECTIONS] = {(dr.CONNECTION_NETWORK_MAC, zigbee.mac_address)}
|
||||||
|
|
||||||
@ -63,25 +80,27 @@ async def async_setup_devices(bridge: "HueBridge"):
|
|||||||
dev_reg.async_remove_device(device.id)
|
dev_reg.async_remove_device(device.id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def handle_device_event(evt_type: EventType, hue_device: Device) -> None:
|
def handle_device_event(
|
||||||
"""Handle event from Hue devices controller."""
|
evt_type: EventType, hue_resource: Device | Room | Zone
|
||||||
|
) -> None:
|
||||||
|
"""Handle event from Hue controller."""
|
||||||
if evt_type == EventType.RESOURCE_DELETED:
|
if evt_type == EventType.RESOURCE_DELETED:
|
||||||
remove_device(hue_device.id)
|
remove_device(hue_resource.id)
|
||||||
else:
|
else:
|
||||||
# updates to existing device will also be handled by this call
|
# updates to existing device will also be handled by this call
|
||||||
add_device(hue_device)
|
add_device(hue_resource)
|
||||||
|
|
||||||
# create/update all current devices found in controller
|
# create/update all current devices found in controllers
|
||||||
known_devices = [add_device(hue_device) for hue_device in dev_controller]
|
known_devices = [add_device(hue_device) for hue_device in dev_controller]
|
||||||
|
known_devices += [add_device(hue_room) for hue_room in api.groups.room]
|
||||||
|
known_devices += [add_device(hue_zone) for hue_zone in api.groups.zone]
|
||||||
|
|
||||||
# Check for nodes that no longer exist and remove them
|
# Check for nodes that no longer exist and remove them
|
||||||
for device in dr.async_entries_for_config_entry(dev_reg, entry.entry_id):
|
for device in dr.async_entries_for_config_entry(dev_reg, entry.entry_id):
|
||||||
if device not in known_devices:
|
if device not in known_devices:
|
||||||
# handle case where a virtual device was created for a Hue group
|
|
||||||
hue_dev_id = next(x[1] for x in device.identifiers if x[0] == DOMAIN)
|
|
||||||
if hue_dev_id in api.groups:
|
|
||||||
continue
|
|
||||||
dev_reg.async_remove_device(device.id)
|
dev_reg.async_remove_device(device.id)
|
||||||
|
|
||||||
# add listener for updates on Hue devices controller
|
# add listener for updates on Hue controllers
|
||||||
entry.async_on_unload(dev_controller.subscribe(handle_device_event))
|
entry.async_on_unload(dev_controller.subscribe(handle_device_event))
|
||||||
|
entry.async_on_unload(api.groups.room.subscribe(handle_device_event))
|
||||||
|
entry.async_on_unload(api.groups.zone.subscribe(handle_device_event))
|
||||||
|
@ -9,10 +9,7 @@ from aiohue.v2.models.resource import ResourceTypes
|
|||||||
from aiohue.v2.models.zigbee_connectivity import ConnectivityServiceStatus
|
from aiohue.v2.models.zigbee_connectivity import ConnectivityServiceStatus
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.device_registry import (
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
DeviceInfo,
|
|
||||||
async_get as async_get_device_registry,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||||
|
|
||||||
@ -72,24 +69,6 @@ class HueBaseEntity(Entity):
|
|||||||
self._ignore_availability = None
|
self._ignore_availability = None
|
||||||
self._last_state = None
|
self._last_state = None
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return name for the entity."""
|
|
||||||
if self.device is None:
|
|
||||||
# this is just a guard
|
|
||||||
# creating a pretty name for device-less entities (e.g. groups/scenes)
|
|
||||||
# should be handled in the platform instead
|
|
||||||
return self.resource.type.value
|
|
||||||
dev_name = self.device.metadata.name
|
|
||||||
# if resource is a light, use the device name itself
|
|
||||||
if self.resource.type == ResourceTypes.LIGHT:
|
|
||||||
return dev_name
|
|
||||||
# for sensors etc, use devicename + pretty name of type
|
|
||||||
type_title = RESOURCE_TYPE_NAMES.get(
|
|
||||||
self.resource.type, self.resource.type.value.replace("_", " ").title()
|
|
||||||
)
|
|
||||||
return f"{dev_name} {type_title}"
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call when entity is added."""
|
"""Call when entity is added."""
|
||||||
self._check_availability()
|
self._check_availability()
|
||||||
@ -146,19 +125,12 @@ class HueBaseEntity(Entity):
|
|||||||
def _handle_event(self, event_type: EventType, resource: HueResource) -> None:
|
def _handle_event(self, event_type: EventType, resource: HueResource) -> None:
|
||||||
"""Handle status event for this resource (or it's parent)."""
|
"""Handle status event for this resource (or it's parent)."""
|
||||||
if event_type == EventType.RESOURCE_DELETED:
|
if event_type == EventType.RESOURCE_DELETED:
|
||||||
# handle removal of room and zone 'virtual' devices/services
|
|
||||||
# regular devices are removed automatically by the logic in device.py.
|
|
||||||
if resource.type in (ResourceTypes.ROOM, ResourceTypes.ZONE):
|
|
||||||
dev_reg = async_get_device_registry(self.hass)
|
|
||||||
if device := dev_reg.async_get_device(
|
|
||||||
identifiers={(DOMAIN, resource.id)}
|
|
||||||
):
|
|
||||||
dev_reg.async_remove_device(device.id)
|
|
||||||
# cleanup entities that are not strictly device-bound and have the bridge as parent
|
# cleanup entities that are not strictly device-bound and have the bridge as parent
|
||||||
if self.device is None:
|
if self.device is None and resource.id == self.resource.id:
|
||||||
ent_reg = async_get_entity_registry(self.hass)
|
ent_reg = async_get_entity_registry(self.hass)
|
||||||
ent_reg.async_remove(self.entity_id)
|
ent_reg.async_remove(self.entity_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.debug("Received status update for %s", self.entity_id)
|
self.logger.debug("Received status update for %s", self.entity_id)
|
||||||
self._check_availability()
|
self._check_availability()
|
||||||
self.on_update()
|
self.on_update()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Support for Hue groups (room/zone)."""
|
"""Support for Hue groups (room/zone)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohue.v2 import HueBridgeV2
|
from aiohue.v2 import HueBridgeV2
|
||||||
@ -17,11 +18,12 @@ from homeassistant.components.light import (
|
|||||||
FLASH_SHORT,
|
FLASH_SHORT,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
|
LightEntityDescription,
|
||||||
LightEntityFeature,
|
LightEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from ..bridge import HueBridge
|
from ..bridge import HueBridge
|
||||||
@ -43,18 +45,26 @@ async def async_setup_entry(
|
|||||||
bridge: HueBridge = hass.data[DOMAIN][config_entry.entry_id]
|
bridge: HueBridge = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
api: HueBridgeV2 = bridge.api
|
api: HueBridgeV2 = bridge.api
|
||||||
|
|
||||||
@callback
|
async def async_add_light(event_type: EventType, resource: GroupedLight) -> None:
|
||||||
def async_add_light(event_type: EventType, resource: GroupedLight) -> None:
|
|
||||||
"""Add Grouped Light for Hue Room/Zone."""
|
"""Add Grouped Light for Hue Room/Zone."""
|
||||||
group = api.groups.grouped_light.get_zone(resource.id)
|
# delay group creation a bit due to a race condition where the
|
||||||
|
# grouped_light resource is created before the zone/room
|
||||||
|
retries = 5
|
||||||
|
while (
|
||||||
|
retries
|
||||||
|
and (group := api.groups.grouped_light.get_zone(resource.id)) is None
|
||||||
|
):
|
||||||
|
retries -= 1
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
if group is None:
|
if group is None:
|
||||||
|
# guard, just in case
|
||||||
return
|
return
|
||||||
light = GroupedHueLight(bridge, resource, group)
|
light = GroupedHueLight(bridge, resource, group)
|
||||||
async_add_entities([light])
|
async_add_entities([light])
|
||||||
|
|
||||||
# add current items
|
# add current items
|
||||||
for item in api.groups.grouped_light.items:
|
for item in api.groups.grouped_light.items:
|
||||||
async_add_light(EventType.RESOURCE_ADDED, item)
|
await async_add_light(EventType.RESOURCE_ADDED, item)
|
||||||
|
|
||||||
# register listener for new grouped_light
|
# register listener for new grouped_light
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
@ -67,7 +77,12 @@ async def async_setup_entry(
|
|||||||
class GroupedHueLight(HueBaseEntity, LightEntity):
|
class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||||
"""Representation of a Grouped Hue light."""
|
"""Representation of a Grouped Hue light."""
|
||||||
|
|
||||||
_attr_icon = "mdi:lightbulb-group"
|
entity_description = LightEntityDescription(
|
||||||
|
key="hue_grouped_light",
|
||||||
|
icon="mdi:lightbulb-group",
|
||||||
|
has_entity_name=True,
|
||||||
|
name=None,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, bridge: HueBridge, resource: GroupedLight, group: Room | Zone
|
self, bridge: HueBridge, resource: GroupedLight, group: Room | Zone
|
||||||
@ -81,7 +96,11 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
|||||||
self.api: HueBridgeV2 = bridge.api
|
self.api: HueBridgeV2 = bridge.api
|
||||||
self._attr_supported_features |= LightEntityFeature.FLASH
|
self._attr_supported_features |= LightEntityFeature.FLASH
|
||||||
self._attr_supported_features |= LightEntityFeature.TRANSITION
|
self._attr_supported_features |= LightEntityFeature.TRANSITION
|
||||||
|
# we create a virtual service/device for Hue zones/rooms
|
||||||
|
# so we have a parent for grouped lights and scenes
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self.group.id)},
|
||||||
|
)
|
||||||
self._dynamic_mode_active = False
|
self._dynamic_mode_active = False
|
||||||
self._update_values()
|
self._update_values()
|
||||||
|
|
||||||
@ -103,11 +122,6 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
|||||||
self.api.lights.subscribe(self._handle_event, light_ids)
|
self.api.lights.subscribe(self._handle_event, light_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return name of room/zone for this grouped light."""
|
|
||||||
return self.group.metadata.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if light is on."""
|
"""Return true if light is on."""
|
||||||
@ -131,22 +145,6 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
|
|||||||
"dynamics": self._dynamic_mode_active,
|
"dynamics": self._dynamic_mode_active,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return device (service) info."""
|
|
||||||
# we create a virtual service/device for Hue zones/rooms
|
|
||||||
# so we have a parent for grouped lights and scenes
|
|
||||||
model = self.group.type.value.title()
|
|
||||||
return DeviceInfo(
|
|
||||||
identifiers={(DOMAIN, self.group.id)},
|
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
name=self.group.metadata.name,
|
|
||||||
manufacturer=self.api.config.bridge_device.product_data.manufacturer_name,
|
|
||||||
model=model,
|
|
||||||
suggested_area=self.group.metadata.name if model == "Room" else None,
|
|
||||||
via_device=(DOMAIN, self.api.config.bridge_device.id),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the grouped_light on."""
|
"""Turn the grouped_light on."""
|
||||||
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
|
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
|
||||||
|
@ -19,6 +19,7 @@ from homeassistant.components.light import (
|
|||||||
FLASH_SHORT,
|
FLASH_SHORT,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
|
LightEntityDescription,
|
||||||
LightEntityFeature,
|
LightEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -69,6 +70,10 @@ async def async_setup_entry(
|
|||||||
class HueLight(HueBaseEntity, LightEntity):
|
class HueLight(HueBaseEntity, LightEntity):
|
||||||
"""Representation of a Hue light."""
|
"""Representation of a Hue light."""
|
||||||
|
|
||||||
|
entity_description = LightEntityDescription(
|
||||||
|
key="hue_light", has_entity_name=True, name=None
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, bridge: HueBridge, controller: LightsController, resource: Light
|
self, bridge: HueBridge, controller: LightsController, resource: Light
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -20,6 +20,7 @@ from aiohue.v2.models.zigbee_connectivity import ZigbeeConnectivity
|
|||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -93,9 +94,13 @@ class HueSensorBase(HueBaseEntity, SensorEntity):
|
|||||||
class HueTemperatureSensor(HueSensorBase):
|
class HueTemperatureSensor(HueSensorBase):
|
||||||
"""Representation of a Hue Temperature sensor."""
|
"""Representation of a Hue Temperature sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
|
entity_description = SensorEntityDescription(
|
||||||
_attr_device_class = SensorDeviceClass.TEMPERATURE
|
key="temperature_sensor",
|
||||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
has_entity_name=True,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float:
|
def native_value(self) -> float:
|
||||||
@ -106,9 +111,13 @@ class HueTemperatureSensor(HueSensorBase):
|
|||||||
class HueLightLevelSensor(HueSensorBase):
|
class HueLightLevelSensor(HueSensorBase):
|
||||||
"""Representation of a Hue LightLevel (illuminance) sensor."""
|
"""Representation of a Hue LightLevel (illuminance) sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = LIGHT_LUX
|
entity_description = SensorEntityDescription(
|
||||||
_attr_device_class = SensorDeviceClass.ILLUMINANCE
|
key="lightlevel_sensor",
|
||||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||||
|
native_unit_of_measurement=LIGHT_LUX,
|
||||||
|
has_entity_name=True,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> int:
|
def native_value(self) -> int:
|
||||||
@ -130,10 +139,14 @@ class HueLightLevelSensor(HueSensorBase):
|
|||||||
class HueBatterySensor(HueSensorBase):
|
class HueBatterySensor(HueSensorBase):
|
||||||
"""Representation of a Hue Battery sensor."""
|
"""Representation of a Hue Battery sensor."""
|
||||||
|
|
||||||
_attr_native_unit_of_measurement = PERCENTAGE
|
entity_description = SensorEntityDescription(
|
||||||
_attr_device_class = SensorDeviceClass.BATTERY
|
key="battery_sensor",
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
has_entity_name=True,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> int:
|
def native_value(self) -> int:
|
||||||
@ -151,16 +164,20 @@ class HueBatterySensor(HueSensorBase):
|
|||||||
class HueZigbeeConnectivitySensor(HueSensorBase):
|
class HueZigbeeConnectivitySensor(HueSensorBase):
|
||||||
"""Representation of a Hue ZigbeeConnectivity sensor."""
|
"""Representation of a Hue ZigbeeConnectivity sensor."""
|
||||||
|
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
entity_description = SensorEntityDescription(
|
||||||
_attr_translation_key = "zigbee_connectivity"
|
key="zigbee_connectivity_sensor",
|
||||||
_attr_device_class = SensorDeviceClass.ENUM
|
device_class=SensorDeviceClass.ENUM,
|
||||||
_attr_options = [
|
has_entity_name=True,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
translation_key="zigbee_connectivity",
|
||||||
|
options=[
|
||||||
"connected",
|
"connected",
|
||||||
"disconnected",
|
"disconnected",
|
||||||
"connectivity_issue",
|
"connectivity_issue",
|
||||||
"unidirectional_incoming",
|
"unidirectional_incoming",
|
||||||
]
|
],
|
||||||
_attr_entity_registry_enabled_default = False
|
entity_registry_enabled_default=False,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str:
|
def native_value(self) -> str:
|
||||||
|
@ -3,6 +3,7 @@ import asyncio
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import aiohue.v1 as aiohue_v1
|
import aiohue.v1 as aiohue_v1
|
||||||
@ -12,6 +13,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components import hue
|
from homeassistant.components import hue
|
||||||
from homeassistant.components.hue.v1 import sensor_base as hue_sensor_base
|
from homeassistant.components.hue.v1 import sensor_base as hue_sensor_base
|
||||||
|
from homeassistant.components.hue.v2.device import async_setup_devices
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
@ -20,6 +22,7 @@ from tests.common import (
|
|||||||
load_fixture,
|
load_fixture,
|
||||||
mock_device_registry,
|
mock_device_registry,
|
||||||
)
|
)
|
||||||
|
from tests.components.hue.const import FAKE_BRIDGE, FAKE_BRIDGE_DEVICE
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@ -56,6 +59,8 @@ def create_mock_bridge(hass, api_version=1):
|
|||||||
async def async_initialize_bridge():
|
async def async_initialize_bridge():
|
||||||
if bridge.config_entry:
|
if bridge.config_entry:
|
||||||
hass.data.setdefault(hue.DOMAIN, {})[bridge.config_entry.entry_id] = bridge
|
hass.data.setdefault(hue.DOMAIN, {})[bridge.config_entry.entry_id] = bridge
|
||||||
|
if bridge.api_version == 2:
|
||||||
|
await async_setup_devices(bridge)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
bridge.async_initialize_bridge = async_initialize_bridge
|
bridge.async_initialize_bridge = async_initialize_bridge
|
||||||
@ -140,22 +145,10 @@ def create_mock_api_v2(hass):
|
|||||||
"""Create a mock V2 API."""
|
"""Create a mock V2 API."""
|
||||||
api = Mock(spec=aiohue_v2.HueBridgeV2)
|
api = Mock(spec=aiohue_v2.HueBridgeV2)
|
||||||
api.initialize = AsyncMock()
|
api.initialize = AsyncMock()
|
||||||
api.config = Mock(
|
|
||||||
bridge_id="aabbccddeeffggh",
|
|
||||||
mac_address="00:17:88:01:aa:bb:fd:c7",
|
|
||||||
model_id="BSB002",
|
|
||||||
api_version="9.9.9",
|
|
||||||
software_version="1935144040",
|
|
||||||
bridge_device=Mock(
|
|
||||||
id="4a507550-8742-4087-8bf5-c2334f29891c",
|
|
||||||
product_data=Mock(manufacturer_name="Mock"),
|
|
||||||
),
|
|
||||||
spec=aiohue_v2.ConfigController,
|
|
||||||
)
|
|
||||||
api.config.name = "Home"
|
|
||||||
api.mock_requests = []
|
api.mock_requests = []
|
||||||
|
|
||||||
api.logger = logging.getLogger(__name__)
|
api.logger = logging.getLogger(__name__)
|
||||||
|
api.config = aiohue_v2.ConfigController(api)
|
||||||
api.events = aiohue_v2.EventStream(api)
|
api.events = aiohue_v2.EventStream(api)
|
||||||
api.devices = aiohue_v2.DevicesController(api)
|
api.devices = aiohue_v2.DevicesController(api)
|
||||||
api.lights = aiohue_v2.LightsController(api)
|
api.lights = aiohue_v2.LightsController(api)
|
||||||
@ -171,9 +164,13 @@ def create_mock_api_v2(hass):
|
|||||||
|
|
||||||
api.request = mock_request
|
api.request = mock_request
|
||||||
|
|
||||||
async def load_test_data(data):
|
async def load_test_data(data: list[dict[str, Any]]):
|
||||||
"""Load test data into controllers."""
|
"""Load test data into controllers."""
|
||||||
api.config = aiohue_v2.ConfigController(api)
|
|
||||||
|
# append default bridge if none explicitly given in test data
|
||||||
|
if not any(x for x in data if x["type"] == "bridge"):
|
||||||
|
data.append(FAKE_BRIDGE)
|
||||||
|
data.append(FAKE_BRIDGE_DEVICE)
|
||||||
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
api.config.initialize(data),
|
api.config.initialize(data),
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
"""Constants for Hue tests."""
|
"""Constants for Hue tests."""
|
||||||
|
|
||||||
|
FAKE_BRIDGE = {
|
||||||
|
"bridge_id": "aabbccddeeffggh",
|
||||||
|
"id": "07dd5849-abcd-efgh-b9b9-eb540408ce00",
|
||||||
|
"id_v1": "",
|
||||||
|
"owner": {"rid": "4a507550-8742-4087-8bf5-c2334f29891c", "rtype": "device"},
|
||||||
|
"time_zone": {"time_zone": "Europe/Amsterdam"},
|
||||||
|
"type": "bridge",
|
||||||
|
}
|
||||||
|
|
||||||
|
FAKE_BRIDGE_DEVICE = {
|
||||||
|
"id": "4a507550-8742-4087-8bf5-c2334f29891c",
|
||||||
|
"id_v1": "",
|
||||||
|
"metadata": {"archetype": "bridge_v2", "name": "Philips hue"},
|
||||||
|
"product_data": {
|
||||||
|
"certified": True,
|
||||||
|
"manufacturer_name": "Signify Netherlands B.V.",
|
||||||
|
"model_id": "BSB002",
|
||||||
|
"product_archetype": "bridge_v2",
|
||||||
|
"product_name": "Philips hue",
|
||||||
|
"software_version": "1.50.1950111030",
|
||||||
|
},
|
||||||
|
"services": [{"rid": "07dd5849-abcd-efgh-b9b9-eb540408ce00", "rtype": "bridge"}],
|
||||||
|
"type": "device",
|
||||||
|
}
|
||||||
|
|
||||||
FAKE_DEVICE = {
|
FAKE_DEVICE = {
|
||||||
"id": "fake_device_id_1",
|
"id": "fake_device_id_1",
|
||||||
|
@ -25,19 +25,17 @@ async def test_binary_sensors(
|
|||||||
assert sensor.attributes["device_class"] == "motion"
|
assert sensor.attributes["device_class"] == "motion"
|
||||||
|
|
||||||
# test entertainment room active sensor
|
# test entertainment room active sensor
|
||||||
sensor = hass.states.get(
|
sensor = hass.states.get("binary_sensor.entertainmentroom_1")
|
||||||
"binary_sensor.entertainmentroom_1_entertainment_configuration"
|
|
||||||
)
|
|
||||||
assert sensor is not None
|
assert sensor is not None
|
||||||
assert sensor.state == "off"
|
assert sensor.state == "off"
|
||||||
assert sensor.name == "Entertainmentroom 1: Entertainment Configuration"
|
assert sensor.name == "Entertainmentroom 1"
|
||||||
assert sensor.attributes["device_class"] == "running"
|
assert sensor.attributes["device_class"] == "running"
|
||||||
|
|
||||||
# test contact sensor
|
# test contact sensor
|
||||||
sensor = hass.states.get("binary_sensor.test_contact_sensor_contact")
|
sensor = hass.states.get("binary_sensor.test_contact_sensor_opening")
|
||||||
assert sensor is not None
|
assert sensor is not None
|
||||||
assert sensor.state == "off"
|
assert sensor.state == "off"
|
||||||
assert sensor.name == "Test contact sensor Contact"
|
assert sensor.name == "Test contact sensor Opening"
|
||||||
assert sensor.attributes["device_class"] == "opening"
|
assert sensor.attributes["device_class"] == "opening"
|
||||||
# test contact sensor disabled == state unknown
|
# test contact sensor disabled == state unknown
|
||||||
mock_bridge_v2.api.emit_event(
|
mock_bridge_v2.api.emit_event(
|
||||||
@ -49,7 +47,7 @@ async def test_binary_sensors(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
sensor = hass.states.get("binary_sensor.test_contact_sensor_contact")
|
sensor = hass.states.get("binary_sensor.test_contact_sensor_opening")
|
||||||
assert sensor.state == "unknown"
|
assert sensor.state == "unknown"
|
||||||
|
|
||||||
# test tamper sensor
|
# test tamper sensor
|
||||||
|
@ -57,7 +57,7 @@ async def test_sensor_add_update(hass: HomeAssistant, mock_bridge_v2) -> None:
|
|||||||
await mock_bridge_v2.api.load_test_data([FAKE_DEVICE, FAKE_ZIGBEE_CONNECTIVITY])
|
await mock_bridge_v2.api.load_test_data([FAKE_DEVICE, FAKE_ZIGBEE_CONNECTIVITY])
|
||||||
await setup_platform(hass, mock_bridge_v2, "event")
|
await setup_platform(hass, mock_bridge_v2, "event")
|
||||||
|
|
||||||
test_entity_id = "event.hue_mocked_device_relative_rotary"
|
test_entity_id = "event.hue_mocked_device_rotary"
|
||||||
|
|
||||||
# verify entity does not exist before we start
|
# verify entity does not exist before we start
|
||||||
assert hass.states.get(test_entity_id) is None
|
assert hass.states.get(test_entity_id) is None
|
||||||
@ -70,7 +70,7 @@ async def test_sensor_add_update(hass: HomeAssistant, mock_bridge_v2) -> None:
|
|||||||
state = hass.states.get(test_entity_id)
|
state = hass.states.get(test_entity_id)
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == "unknown"
|
assert state.state == "unknown"
|
||||||
assert state.name == "Hue mocked device Relative Rotary"
|
assert state.name == "Hue mocked device Rotary"
|
||||||
# check event_types
|
# check event_types
|
||||||
assert state.attributes[ATTR_EVENT_TYPES] == ["clock_wise", "counter_clock_wise"]
|
assert state.attributes[ATTR_EVENT_TYPES] == ["clock_wise", "counter_clock_wise"]
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ async def test_scene_updates(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
test_entity = hass.states.get(test_entity_id)
|
test_entity = hass.states.get(test_entity_id)
|
||||||
assert test_entity.name == "Test Room 2 Mocked Scene"
|
assert test_entity.attributes["group_name"] == "Test Room 2"
|
||||||
|
|
||||||
# # test delete
|
# # test delete
|
||||||
mock_bridge_v2.api.emit_event("delete", updated_resource)
|
mock_bridge_v2.api.emit_event("delete", updated_resource)
|
||||||
|
@ -18,9 +18,9 @@ async def test_switch(
|
|||||||
assert len(hass.states.async_all()) == 4
|
assert len(hass.states.async_all()) == 4
|
||||||
|
|
||||||
# test config switch to enable/disable motion sensor
|
# test config switch to enable/disable motion sensor
|
||||||
test_entity = hass.states.get("switch.hue_motion_sensor_motion")
|
test_entity = hass.states.get("switch.hue_motion_sensor_motion_sensor_enabled")
|
||||||
assert test_entity is not None
|
assert test_entity is not None
|
||||||
assert test_entity.name == "Hue motion sensor Motion"
|
assert test_entity.name == "Hue motion sensor Motion sensor enabled"
|
||||||
assert test_entity.state == "on"
|
assert test_entity.state == "on"
|
||||||
assert test_entity.attributes["device_class"] == "switch"
|
assert test_entity.attributes["device_class"] == "switch"
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ async def test_switch_turn_on_service(
|
|||||||
|
|
||||||
await setup_platform(hass, mock_bridge_v2, "switch")
|
await setup_platform(hass, mock_bridge_v2, "switch")
|
||||||
|
|
||||||
test_entity_id = "switch.hue_motion_sensor_motion"
|
test_entity_id = "switch.hue_motion_sensor_motion_sensor_enabled"
|
||||||
|
|
||||||
# call the HA turn_on service
|
# call the HA turn_on service
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -64,7 +64,7 @@ async def test_switch_turn_off_service(
|
|||||||
|
|
||||||
await setup_platform(hass, mock_bridge_v2, "switch")
|
await setup_platform(hass, mock_bridge_v2, "switch")
|
||||||
|
|
||||||
test_entity_id = "switch.hue_motion_sensor_motion"
|
test_entity_id = "switch.hue_motion_sensor_motion_sensor_enabled"
|
||||||
|
|
||||||
# verify the switch is on before we start
|
# verify the switch is on before we start
|
||||||
assert hass.states.get(test_entity_id).state == "on"
|
assert hass.states.get(test_entity_id).state == "on"
|
||||||
@ -103,7 +103,7 @@ async def test_switch_added(hass: HomeAssistant, mock_bridge_v2) -> None:
|
|||||||
|
|
||||||
await setup_platform(hass, mock_bridge_v2, "switch")
|
await setup_platform(hass, mock_bridge_v2, "switch")
|
||||||
|
|
||||||
test_entity_id = "switch.hue_mocked_device_motion"
|
test_entity_id = "switch.hue_mocked_device_motion_sensor_enabled"
|
||||||
|
|
||||||
# verify entity does not exist before we start
|
# verify entity does not exist before we start
|
||||||
assert hass.states.get(test_entity_id) is None
|
assert hass.states.get(test_entity_id) is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user