Give scenes last activated state (#62673)
This commit is contained in:
parent
e03283292b
commit
3f7275a9c7
7 changed files with 93 additions and 27 deletions
|
@ -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."""
|
||||
|
|
|
@ -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, {})
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue