mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 08:51:46 -04:00
Add switch to enable/disable Hue MotionAware zones (#177415)
This commit is contained in:
@@ -146,6 +146,9 @@
|
||||
"light_sensor_enabled": {
|
||||
"name": "Light sensor enabled"
|
||||
},
|
||||
"motion_aware": {
|
||||
"name": "MotionAware"
|
||||
},
|
||||
"motion_sensor_enabled": {
|
||||
"name": "Motion sensor enabled"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ from collections.abc import Callable
|
||||
from typing import Any, override
|
||||
|
||||
from aiohue.v2 import HueBridgeV2
|
||||
from aiohue.v2.controllers.config import BehaviorInstance, BehaviorInstanceController
|
||||
from aiohue.v2.controllers.config import (
|
||||
BehaviorInstance,
|
||||
BehaviorInstanceController,
|
||||
MotionAreaConfiguration,
|
||||
MotionAreaConfigurationController,
|
||||
)
|
||||
from aiohue.v2.controllers.events import EventType
|
||||
from aiohue.v2.controllers.sensors import (
|
||||
LightLevel,
|
||||
@@ -23,9 +28,10 @@ from homeassistant.components.switch import (
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .bridge import HueConfigEntry
|
||||
from .bridge import HueBridge, HueConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .v2.entity import HueBaseEntity
|
||||
|
||||
@@ -47,17 +53,20 @@ async def async_setup_entry(
|
||||
def register_items(
|
||||
controller: BehaviorInstanceController
|
||||
| LightLevelController
|
||||
| MotionAreaConfigurationController
|
||||
| MotionController,
|
||||
switch_class: type[
|
||||
HueBehaviorInstanceEnabledEntity
|
||||
| HueLightSensorEnabledEntity
|
||||
| HueMotionAreaConfigurationEnabledEntity
|
||||
| HueMotionSensorEnabledEntity
|
||||
],
|
||||
resource_filter: Callable[[Any], bool] | None = None,
|
||||
):
|
||||
@callback
|
||||
def async_add_entity(
|
||||
event_type: EventType, resource: BehaviorInstance | LightLevel | Motion
|
||||
event_type: EventType,
|
||||
resource: BehaviorInstance | LightLevel | MotionAreaConfiguration | Motion,
|
||||
) -> None:
|
||||
"""Add entity from Hue resource."""
|
||||
if resource_filter is not None and not resource_filter(resource):
|
||||
@@ -107,13 +116,21 @@ async def async_setup_entry(
|
||||
HueBehaviorInstanceEnabledEntity,
|
||||
is_user_automation,
|
||||
)
|
||||
register_items(
|
||||
api.config.motion_area_configuration, HueMotionAreaConfigurationEnabledEntity
|
||||
)
|
||||
|
||||
|
||||
class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
|
||||
"""Represent a Switch entity from a Hue resource that toggles."""
|
||||
|
||||
controller: BehaviorInstanceController | LightLevelController | MotionController
|
||||
resource: BehaviorInstance | LightLevel | Motion
|
||||
controller: (
|
||||
BehaviorInstanceController
|
||||
| LightLevelController
|
||||
| MotionAreaConfigurationController
|
||||
| MotionController
|
||||
)
|
||||
resource: BehaviorInstance | LightLevel | MotionAreaConfiguration | Motion
|
||||
|
||||
entity_description = SwitchEntityDescription(
|
||||
key="sensing_service_enabled",
|
||||
@@ -190,6 +207,35 @@ class HueBehaviorInstanceEnabledEntity(HueResourceEnabledEntity):
|
||||
await self.bridge.async_request_call(self.controller.stop, self.resource.id)
|
||||
|
||||
|
||||
class HueMotionAreaConfigurationEnabledEntity(HueResourceEnabledEntity):
|
||||
"""Representation of a Switch entity to enable/disable a Hue MotionAware zone."""
|
||||
|
||||
controller: MotionAreaConfigurationController
|
||||
resource: MotionAreaConfiguration
|
||||
|
||||
entity_description = SwitchEntityDescription(
|
||||
key="motion_area_configuration",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
has_entity_name=True,
|
||||
translation_key="motion_aware",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bridge: HueBridge,
|
||||
controller: MotionAreaConfigurationController,
|
||||
resource: MotionAreaConfiguration,
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(bridge, controller, resource)
|
||||
# link the switch to the group the MotionAware zone is associated with
|
||||
self.hue_group = controller.get_group(resource.id)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.hue_group.id)},
|
||||
)
|
||||
|
||||
|
||||
class HueMotionSensorEnabledEntity(HueResourceEnabledEntity):
|
||||
"""Representation of a Switch entity to enable/disable a Hue motion sensor."""
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
from homeassistant.components.hue.const import DOMAIN
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.util.json import JsonArrayType
|
||||
|
||||
from .conftest import setup_platform
|
||||
@@ -21,6 +21,8 @@ from .const import (
|
||||
FAKE_ZIGBEE_CONNECTIVITY,
|
||||
)
|
||||
|
||||
TEST_ROOM_ID = "6ddc9066-7e7d-4a03-a773-c73937968296"
|
||||
|
||||
|
||||
async def test_switch(
|
||||
hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
|
||||
@@ -31,8 +33,8 @@ async def test_switch(
|
||||
await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)
|
||||
# there shouldn't have been any requests at this point
|
||||
assert len(mock_bridge_v2.mock_requests) == 0
|
||||
# 4 entities should be created from test data
|
||||
assert len(hass.states.async_all()) == 4
|
||||
# 5 entities should be created from test data
|
||||
assert len(hass.states.async_all()) == 5
|
||||
|
||||
# test config switch to enable/disable motion sensor
|
||||
test_entity = hass.states.get("switch.hue_motion_sensor_motion_sensor_enabled")
|
||||
@@ -49,6 +51,35 @@ async def test_switch(
|
||||
assert test_entity.state == "on"
|
||||
assert test_entity.attributes["device_class"] == "switch"
|
||||
|
||||
# test config switch to enable/disable a MotionAware zone
|
||||
test_entity = hass.states.get("switch.test_room_test_room_motionaware")
|
||||
assert test_entity is not None
|
||||
assert test_entity.name == "Test Room MotionAware"
|
||||
assert test_entity.state == "on"
|
||||
assert test_entity.attributes["device_class"] == "switch"
|
||||
|
||||
|
||||
async def test_motionaware_switch_device(
|
||||
hass: HomeAssistant,
|
||||
mock_bridge_v2: Mock,
|
||||
v2_resources_test_data: JsonArrayType,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test the MotionAware switch is attached to the zone device, not the bridge."""
|
||||
await mock_bridge_v2.api.load_test_data(v2_resources_test_data)
|
||||
|
||||
await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)
|
||||
|
||||
entity_entry = entity_registry.async_get("switch.test_room_test_room_motionaware")
|
||||
assert entity_entry is not None
|
||||
|
||||
zone_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, TEST_ROOM_ID), mock_bridge_v2.config_entry.entry_id
|
||||
)
|
||||
assert zone_device is not None
|
||||
assert entity_entry.device_id == zone_device.id
|
||||
|
||||
|
||||
async def test_switch_turn_on_service(
|
||||
hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
|
||||
@@ -115,6 +146,62 @@ async def test_switch_turn_off_service(
|
||||
assert test_entity.state == "off"
|
||||
|
||||
|
||||
async def test_motionaware_switch_turn_on_off_service(
|
||||
hass: HomeAssistant, mock_bridge_v2: Mock, v2_resources_test_data: JsonArrayType
|
||||
) -> None:
|
||||
"""Test enabling/disabling a MotionAware zone."""
|
||||
await mock_bridge_v2.api.load_test_data(v2_resources_test_data)
|
||||
|
||||
await setup_platform(hass, mock_bridge_v2, Platform.SWITCH)
|
||||
|
||||
test_entity_id = "switch.test_room_test_room_motionaware"
|
||||
|
||||
# verify the switch is on before we start
|
||||
assert hass.states.get(test_entity_id).state == "on"
|
||||
|
||||
# call the HA turn_off service
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_off",
|
||||
{"entity_id": test_entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# PUT request should have been sent to the motion_area_configuration resource
|
||||
assert len(mock_bridge_v2.mock_requests) == 1
|
||||
assert mock_bridge_v2.mock_requests[0]["method"] == "put"
|
||||
assert (
|
||||
mock_bridge_v2.mock_requests[0]["path"]
|
||||
== "clip/v2/resource/motion_area_configuration/"
|
||||
"5e6f7a8b-9c1d-4e2f-b3a4-5c6d7e8f9a0b"
|
||||
)
|
||||
assert mock_bridge_v2.mock_requests[0]["json"]["enabled"] is False
|
||||
|
||||
# Now generate update event by emitting the json we've sent as incoming event
|
||||
event = {
|
||||
"id": "5e6f7a8b-9c1d-4e2f-b3a4-5c6d7e8f9a0b",
|
||||
"type": "motion_area_configuration",
|
||||
**mock_bridge_v2.mock_requests[0]["json"],
|
||||
}
|
||||
mock_bridge_v2.api.emit_event("update", event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# the switch should now be off
|
||||
assert hass.states.get(test_entity_id).state == "off"
|
||||
|
||||
# call the HA turn_on service
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_on",
|
||||
{"entity_id": test_entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_bridge_v2.mock_requests) == 2
|
||||
assert mock_bridge_v2.mock_requests[1]["method"] == "put"
|
||||
assert mock_bridge_v2.mock_requests[1]["json"]["enabled"] is True
|
||||
|
||||
|
||||
async def test_switch_added(hass: HomeAssistant, mock_bridge_v2: Mock) -> None:
|
||||
"""Test new switch added to bridge."""
|
||||
await mock_bridge_v2.api.load_test_data([FAKE_DEVICE, FAKE_ZIGBEE_CONNECTIVITY])
|
||||
@@ -179,7 +266,7 @@ async def test_internal_behavior_instance_not_added(
|
||||
|
||||
assert hass.states.get("switch.philips_hue_automation_wall_switch_hallway") is None
|
||||
assert hass.states.get("switch.philips_hue_automation_timer_test") is not None
|
||||
assert len(hass.states.async_all()) == 4
|
||||
assert len(hass.states.async_all()) == 5
|
||||
|
||||
|
||||
async def test_internal_behavior_instance_entity_removed(
|
||||
|
||||
Reference in New Issue
Block a user