Skip to content

Commit

Permalink
Adjust Hue integration to use Entity descriptions and translatable en…
Browse files Browse the repository at this point in the history
…tity names (#101413)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
  • Loading branch information
marcelveldt and joostlek committed Oct 9, 2023
1 parent 8a83e81 commit 6393171
Show file tree
Hide file tree
Showing 16 changed files with 266 additions and 209 deletions.
5 changes: 4 additions & 1 deletion homeassistant/components/hue/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class HueButtonEventEntity(HueBaseEntity, EventEntity):
key="button",
device_class=EventDeviceClass.BUTTON,
translation_key="button",
has_entity_name=True,
)

def __init__(self, *args: Any, **kwargs: Any) -> None:
Expand All @@ -89,7 +90,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
@property
def name(self) -> str:
"""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
def _handle_event(self, event_type: EventType, resource: Button) -> None:
Expand All @@ -112,6 +114,7 @@ class HueRotaryEventEntity(HueBaseEntity, EventEntity):
RelativeRotaryDirection.CLOCK_WISE.value,
RelativeRotaryDirection.COUNTER_CLOCK_WISE.value,
],
has_entity_name=True,
)

@callback
Expand Down
29 changes: 10 additions & 19 deletions homeassistant/components/hue/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity
from homeassistant.config_entries import ConfigEntry
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,
async_get_current_platform,
Expand Down Expand Up @@ -86,6 +86,8 @@ def async_add_entity(
class HueSceneEntityBase(HueBaseEntity, SceneEntity):
"""Base Representation of a Scene entity from Hue Scenes."""

_attr_has_entity_name = True

def __init__(
self,
bridge: HueBridge,
Expand All @@ -97,6 +99,11 @@ def __init__(
self.resource = resource
self.controller = controller
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:
"""Call when entity is added."""
Expand All @@ -112,24 +119,8 @@ async def async_added_to_hass(self) -> None:

@property
def name(self) -> str:
"""Return default entity name."""
return f"{self.group.metadata.name} {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),
)
"""Return name of the scene."""
return self.resource.metadata.name


class HueSceneEntity(HueSceneEntityBase):
Expand Down
11 changes: 6 additions & 5 deletions homeassistant/components/hue/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
},
"sensor": {
"zigbee_connectivity": {
"name": "Zigbee connectivity",
"state": {
"connected": "[%key:common::state::connected%]",
"disconnected": "[%key:common::state::disconnected%]",
Expand All @@ -106,11 +107,11 @@
}
},
"switch": {
"automation": {
"state": {
"on": "[%key:common::state::enabled%]",
"off": "[%key:common::state::disabled%]"
}
"motion_sensor_enabled": {
"name": "Motion sensor enabled"
},
"light_sensor_enabled": {
"name": "Light sensor enabled"
}
}
},
Expand Down
79 changes: 48 additions & 31 deletions homeassistant/components/hue/switch.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Support for switch platform for Hue resources (V2 only)."""
from __future__ import annotations

from typing import Any, TypeAlias
from typing import Any

from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.config import BehaviorInstance, BehaviorInstanceController
Expand All @@ -27,12 +27,6 @@
from .const import DOMAIN
from .v2.entity import HueBaseEntity

ControllerType: TypeAlias = (
BehaviorInstanceController | LightLevelController | MotionController
)

SensingService: TypeAlias = LightLevel | Motion


async def async_setup_entry(
hass: HomeAssistant,
Expand All @@ -48,20 +42,22 @@ async def async_setup_entry(
raise NotImplementedError("Switch support is only available for V2 bridges")

@callback
def register_items(controller: ControllerType):
def register_items(
controller: BehaviorInstanceController
| LightLevelController
| MotionController,
switch_class: type[
HueBehaviorInstanceEnabledEntity
| HueLightSensorEnabledEntity
| HueMotionSensorEnabledEntity
],
):
@callback
def async_add_entity(
event_type: EventType, resource: SensingService | BehaviorInstance
event_type: EventType, resource: BehaviorInstance | LightLevel | Motion
) -> None:
"""Add entity from Hue resource."""
if isinstance(resource, BehaviorInstance):
async_add_entities(
[HueBehaviorInstanceEnabledEntity(bridge, controller, resource)]
)
else:
async_add_entities(
[HueSensingServiceEnabledEntity(bridge, controller, resource)]
)
async_add_entities([switch_class(bridge, api.sensors.motion, resource)])

# add all current items in controller
for item in controller:
Expand All @@ -75,15 +71,23 @@ def async_add_entity(
)

# setup for each switch-type hue resource
register_items(api.sensors.motion)
register_items(api.sensors.light_level)
register_items(api.config.behavior_instance)
register_items(api.sensors.motion, HueMotionSensorEnabledEntity)
register_items(api.sensors.light_level, HueLightSensorEnabledEntity)
register_items(api.config.behavior_instance, HueBehaviorInstanceEnabledEntity)


class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
"""Representation of a Switch entity from a Hue resource that can be toggled enabled."""

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
def is_on(self) -> bool:
Expand All @@ -103,16 +107,6 @@ async def async_turn_off(self, **kwargs: Any) -> None:
)


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):
"""Representation of a Switch entity to enable/disable a Hue Behavior Instance."""

Expand All @@ -123,10 +117,33 @@ class HueBehaviorInstanceEnabledEntity(HueResourceEnabledEntity):
device_class=SwitchDeviceClass.SWITCH,
entity_category=EntityCategory.CONFIG,
has_entity_name=False,
translation_key="automation",
)

@property
def name(self) -> str:
"""Return name for this entity."""
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",
)
65 changes: 40 additions & 25 deletions homeassistant/components/hue/v2/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

Expand Down Expand Up @@ -80,25 +82,17 @@ def async_add_sensor(event_type: EventType, resource: SensorType) -> None:
register_items(api.sensors.tamper, HueTamperSensor)


class HueBinarySensorBase(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):
class HueMotionSensor(HueBaseEntity, BinarySensorEntity):
"""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
def is_on(self) -> bool | None:
Expand All @@ -109,10 +103,17 @@ def is_on(self) -> bool | None:
return self.resource.motion.value


class HueEntertainmentActiveSensor(HueBinarySensorBase):
class HueEntertainmentActiveSensor(HueBaseEntity, BinarySensorEntity):
"""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
def is_on(self) -> bool | None:
Expand All @@ -122,14 +123,20 @@ def is_on(self) -> bool | None:
@property
def name(self) -> str:
"""Return sensor name."""
type_title = self.resource.type.value.replace("_", " ").title()
return f"{self.resource.metadata.name}: {type_title}"
return self.resource.metadata.name


class HueContactSensor(HueBinarySensorBase):
class HueContactSensor(HueBaseEntity, BinarySensorEntity):
"""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
def is_on(self) -> bool | None:
Expand All @@ -140,10 +147,18 @@ def is_on(self) -> bool | None:
return self.resource.contact_report.state != ContactState.CONTACT


class HueTamperSensor(HueBinarySensorBase):
class HueTamperSensor(HueBaseEntity, BinarySensorEntity):
"""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
def is_on(self) -> bool | None:
Expand Down
Loading

0 comments on commit 6393171

Please sign in to comment.