Add additional service to set advanced Hue scene options (#63035)

This commit is contained in:
Marcel van der Veldt 2022-01-05 06:39:08 +01:00 committed by GitHub
parent 40e77e2af0
commit f999266c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 9 deletions

View File

@ -5,25 +5,29 @@ from typing import Any
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.scenes import ScenesController
from aiohue.v2.models.scene import Scene as HueScene
from aiohue.v2.controllers.scenes import Scene as HueScene, ScenesController
import voluptuous as vol
from homeassistant.components.light import ATTR_TRANSITION
from homeassistant.components.scene import Scene as SceneEntity
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.entity_platform import AddEntitiesCallback
from homeassistant.helpers import entity_platform
from .bridge import HueBridge
from .const import DOMAIN
from .v2.entity import HueBaseEntity
from .v2.helpers import normalize_hue_transition
from .v2.helpers import normalize_hue_brightness, normalize_hue_transition
SERVICE_ACTIVATE_SCENE = "activate_scene"
ATTR_DYNAMIC = "dynamic"
ATTR_SPEED = "speed"
ATTR_BRIGHTNESS = "brightness"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
async_add_entities: entity_platform.AddEntitiesCallback,
) -> None:
"""Set up scene platform from Hue group scenes."""
bridge: HueBridge = hass.data[DOMAIN][config_entry.entry_id]
@ -48,6 +52,25 @@ async def async_setup_entry(
api.scenes.subscribe(async_add_entity, event_filter=EventType.RESOURCE_ADDED)
)
# add platform service to turn_on/activate scene with advanced options
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_ACTIVATE_SCENE,
{
vol.Optional(ATTR_DYNAMIC): vol.Coerce(bool),
vol.Optional(ATTR_SPEED): vol.All(
vol.Coerce(int), vol.Range(min=0, max=100)
),
vol.Optional(ATTR_TRANSITION): vol.All(
vol.Coerce(float), vol.Range(min=0, max=600)
),
vol.Optional(ATTR_BRIGHTNESS): vol.All(
vol.Coerce(int), vol.Range(min=0, max=255)
),
},
"async_activate",
)
class HueSceneEntity(HueBaseEntity, SceneEntity):
"""Representation of a Scene entity from Hue Scenes."""
@ -97,13 +120,26 @@ class HueSceneEntity(HueBaseEntity, SceneEntity):
async def async_activate(self, **kwargs: Any) -> None:
"""Activate Hue scene."""
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
dynamic = kwargs.get("dynamic", self.is_dynamic)
# the options below are advanced only
# as we're not allowed to override the default scene turn_on service
# we've implemented a `activate_scene` entity service
dynamic = kwargs.get(ATTR_DYNAMIC, False)
speed = kwargs.get(ATTR_SPEED)
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
if speed is not None:
await self.bridge.async_request_call(
self.controller.update,
self.resource.id,
HueScene(self.resource.id, speed=speed / 100),
)
await self.bridge.async_request_call(
self.controller.recall,
self.resource.id,
dynamic=dynamic,
duration=transition,
brightness=brightness,
)
@property

View File

@ -1,5 +1,6 @@
# Describes the format for available hue services
# legacy hue_activate_scene to activate a scene
hue_activate_scene:
name: Activate scene
description: Activate a hue scene stored in the hue hub.
@ -21,3 +22,43 @@ hue_activate_scene:
description: Enable dynamic mode of the scene (V2 bridges and supported scenes only).
selector:
boolean:
# entity service to activate a Hue scene (V2)
activate_scene:
name: Activate Hue Scene
description: Activate a Hue scene with more control over the options.
target:
entity:
domain: scene
integration: hue
fields:
transition:
name: Transition
description: Transition duration it takes to bring devices to the state
defined in the scene.
selector:
number:
min: 0
max: 300
unit_of_measurement: seconds
dynamic:
name: Dynamic
description: Enable dynamic mode of the scene.
selector:
boolean:
speed:
name: Speed
description: Speed of dynamic palette for this scene
advanced: true
selector:
number:
min: 0
max: 100
brightness:
name: Brightness
description: Set brightness for the scene.
advanced: true
selector:
number:
min: 0
max: 255

View File

@ -7,6 +7,7 @@ from typing import Any
from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.groups import GroupedLight, Room, Zone
from aiohue.v2.models.feature import DynamicsFeatureStatus
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -105,7 +106,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
self._attr_entity_registry_enabled_default = bridge.config_entry.options.get(
CONF_ALLOW_HUE_GROUPS, False
)
self._dynamic_mode_active = False
self._update_values()
async def async_added_to_hass(self) -> None:
@ -148,6 +149,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
"hue_scenes": scenes,
"hue_type": self.group.type.value,
"lights": lights,
"dynamics": self._dynamic_mode_active,
}
async def async_turn_on(self, **kwargs: Any) -> None:
@ -261,6 +263,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
total_brightness = 0
all_lights = self.controller.get_lights(self.resource.id)
lights_in_colortemp_mode = 0
lights_in_dynamic_mode = 0
# loop through all lights to find capabilities
for light in all_lights:
if color_temp := light.color_temperature:
@ -278,6 +281,12 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
if dimming := light.dimming:
lights_with_dimming_support += 1
total_brightness += dimming.brightness
if (
light.dynamics
and light.dynamics.status == DynamicsFeatureStatus.DYNAMIC_PALETTE
):
lights_in_dynamic_mode += 1
# this is a bit hacky because light groups may contain lights
# of different capabilities. We set a colormode as supported
# if any of the lights support it
@ -296,6 +305,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
)
else:
supported_color_modes.add(COLOR_MODE_ONOFF)
self._dynamic_mode_active = lights_in_dynamic_mode > 0
self._attr_supported_color_modes = supported_color_modes
# pick a winner for the current colormode
if (

View File

@ -87,6 +87,43 @@ async def test_scene_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
}
async def test_scene_advanced_turn_on_service(
hass, mock_bridge_v2, v2_resources_test_data
):
"""Test calling the advanced turn on service on a scene."""
await mock_bridge_v2.api.load_test_data(v2_resources_test_data)
await setup_platform(hass, mock_bridge_v2, "scene")
test_entity_id = "scene.test_room_regular_test_scene"
# call the hue.activate_scene service
await hass.services.async_call(
"hue",
"activate_scene",
{"entity_id": test_entity_id},
blocking=True,
)
# PUT request should have been sent to device with correct params
assert len(mock_bridge_v2.mock_requests) == 1
assert mock_bridge_v2.mock_requests[0]["method"] == "put"
assert mock_bridge_v2.mock_requests[0]["json"]["recall"] == {"action": "active"}
# test again with sending speed and dynamic
await hass.services.async_call(
"hue",
"activate_scene",
{"entity_id": test_entity_id, "speed": 80, "dynamic": True},
blocking=True,
)
assert len(mock_bridge_v2.mock_requests) == 3
assert mock_bridge_v2.mock_requests[1]["json"]["speed"] == 0.8
assert mock_bridge_v2.mock_requests[2]["json"]["recall"] == {
"action": "dynamic_palette",
}
async def test_scene_updates(hass, mock_bridge_v2, v2_resources_test_data):
"""Test scene events from bridge."""
await mock_bridge_v2.api.load_test_data(v2_resources_test_data)