Give scenes last activated state (#62673)

This commit is contained in:
Franck Nijhof 2022-01-07 19:02:32 +01:00 committed by GitHub
parent e03283292b
commit 3f7275a9c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 93 additions and 27 deletions

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import functools as ft
import importlib
import logging
from typing import Any
from typing import Any, final
import voluptuous as vol
@ -12,14 +12,14 @@ from homeassistant.components.light import ATTR_TRANSITION
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
# mypy: allow-untyped-defs, no-check-untyped-defs
DOMAIN = "scene"
STATE = "scening"
STATES = "states"
@ -71,7 +71,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component.async_register_entity_service(
SERVICE_TURN_ON,
{ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))},
"async_activate",
"_async_activate",
)
return True
@ -89,18 +89,35 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await component.async_unload_entry(entry)
class Scene(Entity):
class Scene(RestoreEntity):
"""A scene is a group of entities and the states we want them to be."""
@property
def should_poll(self) -> bool:
"""No polling needed."""
return False
_attr_should_poll = False
__last_activated: str | None = None
@property
@final
def state(self) -> str | None:
"""Return the state of the scene."""
return STATE
if self.__last_activated is None:
return None
return self.__last_activated
@final
async def _async_activate(self, **kwargs: Any) -> None:
"""Activate scene.
Should not be overridden, handle setting last press timestamp.
"""
self.__last_activated = dt_util.utcnow().isoformat()
self.async_write_ha_state()
await self.async_activate(**kwargs)
async def async_added_to_hass(self) -> None:
"""Call when the button is added to hass."""
state = await self.async_get_last_state()
if state is not None and state.state is not None:
self.__last_activated = state.state
def activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""

View file

@ -800,7 +800,7 @@ async def test_scene_scene(hass):
assert helpers.get_google_type(scene.DOMAIN, None) is not None
assert trait.SceneTrait.supported(scene.DOMAIN, 0, None, None)
trt = trait.SceneTrait(hass, State("scene.bla", scene.STATE), BASIC_CONFIG)
trt = trait.SceneTrait(hass, State("scene.bla", STATE_UNKNOWN), BASIC_CONFIG)
assert trt.sync_attributes() == {}
assert trt.query_attributes() == {}
assert trt.can_execute(trait.COMMAND_ACTIVATE_SCENE, {})

View file

@ -6,6 +6,7 @@ import voluptuous as vol
from homeassistant.components.homeassistant import scene as ha_scene
from homeassistant.components.homeassistant.scene import EVENT_SCENE_RELOADED
from homeassistant.const import STATE_UNKNOWN
from homeassistant.setup import async_setup_component
from tests.common import async_capture_events, async_mock_service
@ -119,7 +120,7 @@ async def test_create_service(hass, caplog):
assert scene is not None
assert scene.domain == "scene"
assert scene.name == "hallo"
assert scene.state == "scening"
assert scene.state == STATE_UNKNOWN
assert scene.attributes.get("entity_id") == ["light.bed_light"]
assert await hass.services.async_call(
@ -137,7 +138,7 @@ async def test_create_service(hass, caplog):
assert scene is not None
assert scene.domain == "scene"
assert scene.name == "hallo"
assert scene.state == "scening"
assert scene.state == STATE_UNKNOWN
assert scene.attributes.get("entity_id") == ["light.kitchen_light"]
assert await hass.services.async_call(
@ -156,7 +157,7 @@ async def test_create_service(hass, caplog):
assert scene is not None
assert scene.domain == "scene"
assert scene.name == "hallo_2"
assert scene.state == "scening"
assert scene.state == STATE_UNKNOWN
assert scene.attributes.get("entity_id") == ["light.kitchen"]

View file

@ -1,6 +1,7 @@
"""Philips Hue scene platform tests for V2 bridge/api."""
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers import entity_registry as er
from .conftest import setup_platform
@ -21,7 +22,7 @@ async def test_scene(hass, mock_bridge_v2, v2_resources_test_data):
test_entity = hass.states.get("scene.test_zone_dynamic_test_scene")
assert test_entity is not None
assert test_entity.name == "Test Zone - Dynamic Test Scene"
assert test_entity.state == "scening"
assert test_entity.state == STATE_UNKNOWN
assert test_entity.attributes["group_name"] == "Test Zone"
assert test_entity.attributes["group_type"] == "zone"
assert test_entity.attributes["name"] == "Dynamic Test Scene"
@ -33,7 +34,7 @@ async def test_scene(hass, mock_bridge_v2, v2_resources_test_data):
test_entity = hass.states.get("scene.test_room_regular_test_scene")
assert test_entity is not None
assert test_entity.name == "Test Room - Regular Test Scene"
assert test_entity.state == "scening"
assert test_entity.state == STATE_UNKNOWN
assert test_entity.attributes["group_name"] == "Test Room"
assert test_entity.attributes["group_type"] == "room"
assert test_entity.attributes["name"] == "Regular Test Scene"
@ -142,7 +143,7 @@ async def test_scene_updates(hass, mock_bridge_v2, v2_resources_test_data):
# the entity should now be available
test_entity = hass.states.get(test_entity_id)
assert test_entity is not None
assert test_entity.state == "scening"
assert test_entity.state == STATE_UNKNOWN
assert test_entity.name == "Test Room - Mocked Scene"
assert test_entity.attributes["brightness"] == 65.0

View file

@ -5,7 +5,7 @@ from unittest.mock import patch
import pytest
from homeassistant.components import scene
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNKNOWN
import homeassistant.core as ha
from homeassistant.setup import async_setup_component
@ -34,7 +34,7 @@ DEFAULT_CONFIG = {
async def test_sending_mqtt_commands(hass, mqtt_mock):
"""Test the sending MQTT commands."""
fake_state = ha.State("scene.test", scene.STATE)
fake_state = ha.State("scene.test", STATE_UNKNOWN)
with patch(
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state",
@ -55,7 +55,7 @@ async def test_sending_mqtt_commands(hass, mqtt_mock):
await hass.async_block_till_done()
state = hass.states.get("scene.test")
assert state.state == scene.STATE
assert state.state == STATE_UNKNOWN
data = {ATTR_ENTITY_ID: "scene.test"}
await hass.services.async_call(scene.DOMAIN, SERVICE_TURN_ON, data, blocking=True)

View file

@ -1,14 +1,22 @@
"""The tests for the Scene component."""
import io
from unittest.mock import patch
import pytest
from homeassistant.components import light, scene
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_ON
from homeassistant.const import (
ATTR_ENTITY_ID,
ENTITY_MATCH_ALL,
SERVICE_TURN_ON,
STATE_UNKNOWN,
)
from homeassistant.core import State
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from homeassistant.util.yaml import loader as yaml_loader
from tests.common import async_mock_service
from tests.common import async_mock_service, mock_restore_cache
@pytest.fixture(autouse=True)
@ -111,8 +119,15 @@ async def test_activate_scene(hass, entities, enable_custom_integrations):
},
)
await hass.async_block_till_done()
assert hass.states.get("scene.test").state == STATE_UNKNOWN
now = dt_util.utcnow()
with patch("homeassistant.core.dt_util.utcnow", return_value=now):
await activate(hass, "scene.test")
assert hass.states.get("scene.test").state == now.isoformat()
assert light.is_on(hass, light_1.entity_id)
assert light.is_on(hass, light_2.entity_id)
assert light_2.last_call("turn_on")[1].get("brightness") == 100
@ -121,17 +136,47 @@ async def test_activate_scene(hass, entities, enable_custom_integrations):
calls = async_mock_service(hass, "light", "turn_on")
now = dt_util.utcnow()
with patch("homeassistant.core.dt_util.utcnow", return_value=now):
await hass.services.async_call(
scene.DOMAIN, "turn_on", {"transition": 42, "entity_id": "scene.test"}
)
await hass.async_block_till_done()
assert hass.states.get("scene.test").state == now.isoformat()
assert len(calls) == 1
assert calls[0].domain == "light"
assert calls[0].service == "turn_on"
assert calls[0].data.get("transition") == 42
async def test_restore_state(hass, entities, enable_custom_integrations):
"""Test we restore state integration."""
mock_restore_cache(hass, (State("scene.test", "2021-01-01T23:59:59+00:00"),))
light_1, light_2 = await setup_lights(hass, entities)
assert await async_setup_component(
hass,
scene.DOMAIN,
{
"scene": [
{
"name": "test",
"entities": {
light_1.entity_id: "on",
light_2.entity_id: "on",
},
}
]
},
)
await hass.async_block_till_done()
assert hass.states.get("scene.test").state == "2021-01-01T23:59:59+00:00"
async def activate(hass, entity_id=ENTITY_MATCH_ALL):
"""Activate a scene."""
data = {}

View file

@ -289,6 +289,8 @@ def scene_factory_fixture(location):
scene = Mock(SceneEntity)
scene.scene_id = str(uuid4())
scene.name = name
scene.icon = None
scene.color = None
scene.location_id = location.location_id
return scene