From bc5a2da7b72d685e687ff4860d873b1600fa1fb4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 21 Apr 2020 03:07:50 +0200 Subject: [PATCH] Add transition support to scenes, cleanup blocking parameter (#34434) --- .../alarm_control_panel/reproduce_state.py | 21 +++++++-- .../components/automation/reproduce_state.py | 21 +++++++-- .../components/climate/reproduce_state.py | 21 +++++++-- .../components/counter/reproduce_state.py | 21 +++++++-- .../components/cover/reproduce_state.py | 33 ++++++++++---- homeassistant/components/deconz/scene.py | 9 ++-- homeassistant/components/elkm1/scene.py | 4 +- .../components/fan/reproduce_state.py | 21 +++++++-- homeassistant/components/fibaro/scene.py | 3 +- .../components/group/reproduce_state.py | 12 +++-- .../components/homeassistant/scene.py | 44 ++++++++++++------- .../hunterdouglas_powerview/scene.py | 3 +- .../input_boolean/reproduce_state.py | 21 +++++++-- .../input_datetime/reproduce_state.py | 21 +++++++-- .../input_number/reproduce_state.py | 21 +++++++-- .../input_select/reproduce_state.py | 21 +++++++-- .../components/input_text/reproduce_state.py | 21 +++++++-- homeassistant/components/knx/scene.py | 4 +- homeassistant/components/lcn/scene.py | 4 +- homeassistant/components/lifx_cloud/scene.py | 7 ++- homeassistant/components/light/__init__.py | 2 +- .../components/light/reproduce_state.py | 26 ++++++++--- homeassistant/components/litejet/scene.py | 3 +- .../components/lock/reproduce_state.py | 21 +++++++-- homeassistant/components/lutron/scene.py | 3 +- .../components/lutron_caseta/scene.py | 3 +- .../media_player/reproduce_state.py | 24 +++++++--- homeassistant/components/nexia/scene.py | 4 +- .../components/remote/reproduce_state.py | 21 +++++++-- homeassistant/components/scene/__init__.py | 22 +++++++--- homeassistant/components/scene/services.yaml | 14 +++++- homeassistant/components/smartthings/scene.py | 4 +- .../components/switch/reproduce_state.py | 21 +++++++-- homeassistant/components/tahoma/scene.py | 10 +++-- .../components/timer/reproduce_state.py | 21 +++++++-- homeassistant/components/tuya/scene.py | 4 +- .../components/vacuum/reproduce_state.py | 25 ++++++++--- homeassistant/components/velux/scene.py | 8 ++-- homeassistant/components/vera/scene.py | 4 +- .../water_heater/reproduce_state.py | 21 +++++++-- homeassistant/components/wink/scene.py | 3 +- homeassistant/helpers/state.py | 7 +-- .../integration/reproduce_state.py | 23 +++++++--- .../test_reproduce_state.py | 8 ++-- .../automation/test_reproduce_state.py | 8 ++-- .../climate/test_reproduce_state.py | 4 +- .../counter/test_reproduce_state.py | 8 ++-- .../components/cover/test_reproduce_state.py | 6 +-- tests/components/fan/test_reproduce_state.py | 4 +- .../components/group/test_reproduce_state.py | 4 +- tests/components/homeassistant/test_scene.py | 18 ++++++++ .../input_boolean/test_reproduce_state.py | 2 - .../input_datetime/test_reproduce_state.py | 3 -- .../input_number/test_reproduce_state.py | 5 +-- .../input_select/test_reproduce_state.py | 8 +--- .../input_text/test_reproduce_state.py | 6 +-- .../components/light/test_reproduce_state.py | 8 ++-- tests/components/lock/test_reproduce_state.py | 4 +- .../media_player/test_reproduce_state.py | 2 +- .../components/remote/test_reproduce_state.py | 4 +- tests/components/scene/test_init.py | 16 ++++++- .../components/switch/test_reproduce_state.py | 6 +-- .../components/timer/test_reproduce_state.py | 4 +- .../components/vacuum/test_reproduce_state.py | 4 +- .../water_heater/test_reproduce_state.py | 6 +-- tests/helpers/test_state.py | 11 +++-- 66 files changed, 547 insertions(+), 229 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/reproduce_state.py b/homeassistant/components/alarm_control_panel/reproduce_state.py index 705bca608a6..9e7d8e6f1a7 100644 --- a/homeassistant/components/alarm_control_panel/reproduce_state.py +++ b/homeassistant/components/alarm_control_panel/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Alarm control panel state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -36,7 +36,11 @@ VALID_STATES = { async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -76,9 +80,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Alarm control panel states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/automation/reproduce_state.py b/homeassistant/components/automation/reproduce_state.py index 4cfe519d585..bcd0cc4e585 100644 --- a/homeassistant/components/automation/reproduce_state.py +++ b/homeassistant/components/automation/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Automation state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -21,7 +21,11 @@ VALID_STATES = {STATE_ON, STATE_OFF} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -53,9 +57,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Automation states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index 82ca4f4e85c..1217d5fde4c 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -1,6 +1,6 @@ """Module that groups code required to handle state restore for component.""" import asyncio -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import Context, State @@ -26,7 +26,11 @@ from .const import ( async def _async_reproduce_states( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" @@ -69,9 +73,18 @@ async def _async_reproduce_states( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" await asyncio.gather( - *(_async_reproduce_states(hass, state, context) for state in states) + *( + _async_reproduce_states( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/counter/reproduce_state.py b/homeassistant/components/counter/reproduce_state.py index b37fcea719e..b2dd63adedc 100644 --- a/homeassistant/components/counter/reproduce_state.py +++ b/homeassistant/components/counter/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Counter state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -21,7 +21,11 @@ _LOGGER = logging.getLogger(__name__) async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -63,9 +67,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Counter states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/cover/reproduce_state.py b/homeassistant/components/cover/reproduce_state.py index 64ea410ce93..2a12172bdab 100644 --- a/homeassistant/components/cover/reproduce_state.py +++ b/homeassistant/components/cover/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Cover state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -33,7 +33,11 @@ VALID_STATES = {STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -61,13 +65,15 @@ async def _async_reproduce_state( service_data = {ATTR_ENTITY_ID: state.entity_id} service_data_tilting = {ATTR_ENTITY_ID: state.entity_id} - if cur_state.state != state.state or cur_state.attributes.get( - ATTR_CURRENT_POSITION - ) != state.attributes.get(ATTR_CURRENT_POSITION): + if not ( + cur_state.state == state.state + and cur_state.attributes.get(ATTR_CURRENT_POSITION) + == state.attributes.get(ATTR_CURRENT_POSITION) + ): # Open/Close - if state.state == STATE_CLOSED or state.state == STATE_CLOSING: + if state.state in [STATE_CLOSED, STATE_CLOSING]: service = SERVICE_CLOSE_COVER - elif state.state == STATE_OPEN or state.state == STATE_OPENING: + elif state.state in [STATE_OPEN, STATE_OPENING]: if ( ATTR_CURRENT_POSITION in cur_state.attributes and ATTR_CURRENT_POSITION in state.attributes @@ -108,10 +114,19 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Cover states.""" # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index a84e799d44d..fdeb1d43acc 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -1,4 +1,6 @@ """Support for deCONZ scenes.""" +from typing import Any + from homeassistant.components.scene import Scene from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -18,10 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @callback def async_add_scene(scenes): """Add scene from deCONZ.""" - entities = [] - - for scene in scenes: - entities.append(DeconzScene(scene, gateway)) + entities = [DeconzScene(scene, gateway) for scene in scenes] async_add_entities(entities) @@ -51,7 +50,7 @@ class DeconzScene(Scene): del self.gateway.deconz_ids[self.entity_id] self._scene = None - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" await self._scene.async_set_state({}) diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index 1f894cc7681..208779b99d0 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -1,4 +1,6 @@ """Support for control of ElkM1 tasks ("macros").""" +from typing import Any + from homeassistant.components.scene import Scene from . import ElkAttachedEntity, create_elk_entities @@ -17,6 +19,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class ElkTask(ElkAttachedEntity, Scene): """Elk-M1 task as scene.""" - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the task.""" self._element.activate() diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index 2692ac7ee5c..55ae78d90f6 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -2,7 +2,7 @@ import asyncio import logging from types import MappingProxyType -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -35,7 +35,11 @@ ATTRIBUTES = { # attribute: service async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -85,11 +89,20 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Fan states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index 06d11bc1f5c..714f019168d 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -1,5 +1,6 @@ """Support for Fibaro scenes.""" import logging +from typing import Any from homeassistant.components.scene import Scene @@ -21,6 +22,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class FibaroScene(FibaroDevice, Scene): """Representation of a Fibaro scene entity.""" - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self.fibaro_device.start() diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index 78790701934..95915412e4f 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -1,5 +1,5 @@ """Module that groups code required to handle state restore for component.""" -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.core import Context, State from homeassistant.helpers.state import async_reproduce_state @@ -9,7 +9,11 @@ from . import get_entity_ids async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" @@ -27,4 +31,6 @@ async def async_reproduce_states( context=state.context, ) ) - await async_reproduce_state(hass, states_copy, blocking=True, context=context) + await async_reproduce_state( + hass, states_copy, context=context, reproduce_options=reproduce_options + ) diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 5eadc39ebed..d14ef438a66 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -1,11 +1,12 @@ """Allow users to set and activate scenes.""" from collections import namedtuple import logging -from typing import List +from typing import Any, List import voluptuous as vol from homeassistant import config as conf_util +from homeassistant.components.light import ATTR_TRANSITION from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene from homeassistant.const import ( ATTR_ENTITY_ID, @@ -62,8 +63,8 @@ def _ensure_no_intersection(value): if ( CONF_SNAPSHOT not in value or CONF_ENTITIES not in value - or not any( - entity_id in value[CONF_SNAPSHOT] for entity_id in value[CONF_ENTITIES] + or all( + entity_id not in value[CONF_SNAPSHOT] for entity_id in value[CONF_ENTITIES] ) ): return value @@ -123,13 +124,11 @@ def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: platform = hass.data[DATA_PLATFORM] - results = [] - - for scene_entity in platform.entities.values(): - if entity_id in scene_entity.scene_config.states: - results.append(scene_entity.entity_id) - - return results + return [ + scene_entity.entity_id + for scene_entity in platform.entities.values() + if entity_id in scene_entity.scene_config.states + ] @callback @@ -171,7 +170,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= conf = await conf_util.async_process_component_config(hass, conf, integration) - if not conf or not platform: + if not (conf and platform): return await platform.async_reset() @@ -189,15 +188,30 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def apply_service(call): """Apply a scene.""" + reproduce_options = {} + + if ATTR_TRANSITION in call.data: + reproduce_options[ATTR_TRANSITION] = call.data.get(ATTR_TRANSITION) + await async_reproduce_state( - hass, call.data[CONF_ENTITIES].values(), blocking=True, context=call.context + hass, + call.data[CONF_ENTITIES].values(), + context=call.context, + reproduce_options=reproduce_options, ) hass.services.async_register( SCENE_DOMAIN, SERVICE_APPLY, apply_service, - vol.Schema({vol.Required(CONF_ENTITIES): STATES_SCHEMA}), + vol.Schema( + { + vol.Optional(ATTR_TRANSITION): vol.All( + vol.Coerce(float), vol.Clamp(min=0, max=6553) + ), + vol.Required(CONF_ENTITIES): STATES_SCHEMA, + } + ), ) async def create_service(call): @@ -289,11 +303,11 @@ class HomeAssistantScene(Scene): attributes[CONF_ID] = unique_id return attributes - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" await async_reproduce_state( self.hass, self.scene_config.states.values(), - blocking=True, context=self._context, + reproduce_options=kwargs, ) diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 1ee82ce165c..b73ce8fd7d5 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -1,5 +1,6 @@ """Support for Powerview scenes from a Powerview hub.""" import logging +from typing import Any from aiopvapi.helpers.aiorequest import AioRequest from aiopvapi.resources.scene import Scene as PvScene @@ -97,6 +98,6 @@ class PowerViewScene(Scene): """Icon to use in the frontend.""" return "mdi:blinds" - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" await self._scene.activate() diff --git a/homeassistant/components/input_boolean/reproduce_state.py b/homeassistant/components/input_boolean/reproduce_state.py index 558d57ae862..d01e931c5cc 100644 --- a/homeassistant/components/input_boolean/reproduce_state.py +++ b/homeassistant/components/input_boolean/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an input boolean state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -19,7 +19,11 @@ _LOGGER = logging.getLogger(__name__) async def _async_reproduce_states( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce input boolean states.""" cur_state = hass.states.get(state.entity_id) @@ -49,9 +53,18 @@ async def _async_reproduce_states( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" await asyncio.gather( - *(_async_reproduce_states(hass, state, context) for state in states) + *( + _async_reproduce_states( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/input_datetime/reproduce_state.py b/homeassistant/components/input_datetime/reproduce_state.py index 17c7fcb9d56..b9eb9800adf 100644 --- a/homeassistant/components/input_datetime/reproduce_state.py +++ b/homeassistant/components/input_datetime/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Input datetime state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -40,7 +40,11 @@ def is_valid_time(string: str) -> bool: async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -97,9 +101,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Input datetime states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/input_number/reproduce_state.py b/homeassistant/components/input_number/reproduce_state.py index a81c7041607..5a6324c4333 100644 --- a/homeassistant/components/input_number/reproduce_state.py +++ b/homeassistant/components/input_number/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Input number state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional import voluptuous as vol @@ -15,7 +15,11 @@ _LOGGER = logging.getLogger(__name__) async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -49,10 +53,19 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Input number states.""" # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index 818510bee4a..bf687738740 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -2,7 +2,7 @@ import asyncio import logging from types import MappingProxyType -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -22,7 +22,11 @@ _LOGGER = logging.getLogger(__name__) async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -64,12 +68,21 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Input select states.""" # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/input_text/reproduce_state.py b/homeassistant/components/input_text/reproduce_state.py index 28a2f27ee84..abd28195d8d 100644 --- a/homeassistant/components/input_text/reproduce_state.py +++ b/homeassistant/components/input_text/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Input text state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -13,7 +13,11 @@ _LOGGER = logging.getLogger(__name__) async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -37,10 +41,19 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Input text states.""" # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index c8c6ac2bcfb..8f2c24c05b6 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,4 +1,6 @@ """Support for KNX scenes.""" +from typing import Any + import voluptuous as vol from xknx.devices import Scene as XknxScene @@ -65,6 +67,6 @@ class KNXScene(Scene): """Return the name of the scene.""" return self.scene.name - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" await self.scene.run() diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index 0dc42639186..498aded0daf 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -1,4 +1,6 @@ """Support for LCN scenes.""" +from typing import Any + import pypck from homeassistant.components.scene import Scene @@ -63,7 +65,7 @@ class LcnScene(LcnDevice, Scene): async def async_added_to_hass(self): """Run when entity about to be added to hass.""" - async def async_activate(self): + def activate(self, **kwargs: Any) -> None: """Activate scene.""" self.address_connection.activate_scene( self.register_id, diff --git a/homeassistant/components/lifx_cloud/scene.py b/homeassistant/components/lifx_cloud/scene.py index 7b0fca67bd6..75ab4656794 100644 --- a/homeassistant/components/lifx_cloud/scene.py +++ b/homeassistant/components/lifx_cloud/scene.py @@ -1,6 +1,7 @@ """Support for LIFX Cloud scenes.""" import asyncio import logging +from typing import Any import aiohttp from aiohttp.hdrs import AUTHORIZATION @@ -46,9 +47,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= status = scenes_resp.status if status == HTTP_OK: data = await scenes_resp.json() - devices = [] - for scene in data: - devices.append(LifxCloudScene(hass, headers, timeout, scene)) + devices = [LifxCloudScene(hass, headers, timeout, scene) for scene in data] async_add_entities(devices) return True if status == 401: @@ -75,7 +74,7 @@ class LifxCloudScene(Scene): """Return the name of the scene.""" return self._name - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" url = f"https://api.lifx.com/v1/scenes/scene_id:{self._uuid}/activate" diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 9750e37f32c..7c33fdcb075 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -40,7 +40,7 @@ SUPPORT_COLOR = 16 SUPPORT_TRANSITION = 32 SUPPORT_WHITE_VALUE = 128 -# Integer that represents transition time in seconds to make change. +# Float that represents transition time in seconds to make change. ATTR_TRANSITION = "transition" # Lists holding color values diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 9a6b22b51a2..a7939beb91e 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -2,7 +2,7 @@ import asyncio import logging from types import MappingProxyType -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -71,7 +71,11 @@ DEPRECATION_WARNING = ( async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -98,7 +102,10 @@ async def _async_reproduce_state( ): return - service_data = {ATTR_ENTITY_ID: state.entity_id} + service_data: Dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id} + + if reproduce_options is not None and ATTR_TRANSITION in reproduce_options: + service_data[ATTR_TRANSITION] = reproduce_options[ATTR_TRANSITION] if state.state == STATE_ON: service = SERVICE_TURN_ON @@ -122,11 +129,20 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Light states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index 12ace3145a1..466ca7296f8 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -1,5 +1,6 @@ """Support for LiteJet scenes.""" import logging +from typing import Any from homeassistant.components import litejet from homeassistant.components.scene import Scene @@ -40,6 +41,6 @@ class LiteJetScene(Scene): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self._lj.activate_scene(self._index) diff --git a/homeassistant/components/lock/reproduce_state.py b/homeassistant/components/lock/reproduce_state.py index b8b469f943f..812b9bf04df 100644 --- a/homeassistant/components/lock/reproduce_state.py +++ b/homeassistant/components/lock/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Lock state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -21,7 +21,11 @@ VALID_STATES = {STATE_LOCKED, STATE_UNLOCKED} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -53,9 +57,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Lock states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py index 4e06c5df626..468597ba5ea 100644 --- a/homeassistant/components/lutron/scene.py +++ b/homeassistant/components/lutron/scene.py @@ -1,5 +1,6 @@ """Support for Lutron scenes.""" import logging +from typing import Any from homeassistant.components.scene import Scene @@ -30,7 +31,7 @@ class LutronScene(LutronDevice, Scene): self._keypad_name = keypad_name self._led = lutron_led - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self._lutron_device.press() diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index 593f58f5274..8b4dc466c90 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -1,5 +1,6 @@ """Support for Lutron Caseta scenes.""" import logging +from typing import Any from homeassistant.components.scene import Scene @@ -34,6 +35,6 @@ class LutronCasetaScene(Scene): """Return the name of the scene.""" return self._scene_name - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" self._bridge.activate_scene(self._scene_id) diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index dc9078d3ffd..a90e4fffdc1 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -1,6 +1,6 @@ """Module that groups code required to handle state restore for component.""" import asyncio -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( SERVICE_MEDIA_PAUSE, @@ -39,14 +39,17 @@ from .const import ( async def _async_reproduce_states( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" async def call_service(service: str, keys: Iterable) -> None: """Call service with set of attributes given.""" - data = {} - data["entity_id"] = state.entity_id + data = {"entity_id": state.entity_id} for key in keys: if key in state.attributes: data[key] = state.attributes[key] @@ -91,9 +94,18 @@ async def _async_reproduce_states( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" await asyncio.gather( - *(_async_reproduce_states(hass, state, context) for state in states) + *( + _async_reproduce_states( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index fb851618aec..12689f05538 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -1,5 +1,7 @@ """Support for Nexia Automations.""" +from typing import Any + from homeassistant.components.scene import Scene from homeassistant.helpers.event import async_call_later @@ -48,7 +50,7 @@ class NexiaAutomationScene(NexiaEntity, Scene): """Return the icon of the automation scene.""" return "mdi:script-text-outline" - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate an automation scene.""" await self.hass.async_add_executor_job(self._automation.activate) diff --git a/homeassistant/components/remote/reproduce_state.py b/homeassistant/components/remote/reproduce_state.py index 1e5dee15683..4e1f426c57b 100644 --- a/homeassistant/components/remote/reproduce_state.py +++ b/homeassistant/components/remote/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Remote state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -21,7 +21,11 @@ VALID_STATES = {STATE_ON, STATE_OFF} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -53,9 +57,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Remote states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 46b06b93698..f4eee8da836 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -1,9 +1,12 @@ """Allow users to set and activate scenes.""" +import functools as ft import importlib import logging +from typing import Any, Optional import voluptuous as vol +from homeassistant.components.light import ATTR_TRANSITION from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.helpers.entity import Entity @@ -62,7 +65,11 @@ async def async_setup(hass, config): await component.async_setup(config) # Ensure Home Assistant platform always loaded. await component.async_setup_platform(HA_DOMAIN, {"platform": HA_DOMAIN, STATES: []}) - component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_activate") + component.async_register_entity_service( + SERVICE_TURN_ON, + {ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))}, + "async_activate", + ) return True @@ -81,19 +88,22 @@ class Scene(Entity): """A scene is a group of entities and the states we want them to be.""" @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed.""" return False @property - def state(self): + def state(self) -> Optional[str]: """Return the state of the scene.""" return STATE - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" raise NotImplementedError() - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" - await self.hass.async_add_job(self.activate) + assert self.hass + task = self.hass.async_add_job(ft.partial(self.activate, **kwargs)) + if task: + await task diff --git a/homeassistant/components/scene/services.yaml b/homeassistant/components/scene/services.yaml index 0c261ed60b5..29fa11e9367 100644 --- a/homeassistant/components/scene/services.yaml +++ b/homeassistant/components/scene/services.yaml @@ -3,6 +3,11 @@ turn_on: description: Activate a scene. fields: + transition: + description: + Transition duration in seconds it takes to bring devices to the state + defined in the scene. + example: 2.5 entity_id: description: Name(s) of scenes to turn on example: "scene.romantic" @@ -11,8 +16,15 @@ reload: description: Reload the scene configuration apply: - description: Activate a scene. Takes same data as the entities field from a single scene in the config. + description: + Activate a scene. Takes same data as the entities field from a single scene + in the config. fields: + transition: + description: + Transition duration in seconds it takes to bring devices to the state + defined in the scene. + example: 2.5 entities: description: The entities and the state that they need to be. example: diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py index a92f2f99ea3..11ee6dc83e1 100644 --- a/homeassistant/components/smartthings/scene.py +++ b/homeassistant/components/smartthings/scene.py @@ -1,4 +1,6 @@ """Support for scenes through the SmartThings cloud API.""" +from typing import Any + from homeassistant.components.scene import Scene from .const import DATA_BROKERS, DOMAIN @@ -17,7 +19,7 @@ class SmartThingsScene(Scene): """Init the scene class.""" self._scene = scene - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate scene.""" await self._scene.execute() diff --git a/homeassistant/components/switch/reproduce_state.py b/homeassistant/components/switch/reproduce_state.py index d2bfc569956..0527f558f35 100644 --- a/homeassistant/components/switch/reproduce_state.py +++ b/homeassistant/components/switch/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Switch state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -21,7 +21,11 @@ VALID_STATES = {STATE_ON, STATE_OFF} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -53,9 +57,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Switch states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py index c60f245fc50..55b89dfcd47 100644 --- a/homeassistant/components/tahoma/scene.py +++ b/homeassistant/components/tahoma/scene.py @@ -1,5 +1,6 @@ """Support for Tahoma scenes.""" import logging +from typing import Any from homeassistant.components.scene import Scene @@ -13,9 +14,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return controller = hass.data[TAHOMA_DOMAIN]["controller"] - scenes = [] - for scene in hass.data[TAHOMA_DOMAIN]["scenes"]: - scenes.append(TahomaScene(scene, controller)) + scenes = [ + TahomaScene(scene, controller) for scene in hass.data[TAHOMA_DOMAIN]["scenes"] + ] + add_entities(scenes, True) @@ -28,7 +30,7 @@ class TahomaScene(Scene): self.controller = controller self._name = self.tahoma_scene.name - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self.controller.launch_action_group(self.tahoma_scene.oid) diff --git a/homeassistant/components/timer/reproduce_state.py b/homeassistant/components/timer/reproduce_state.py index c765ed7da9c..71abb0bfd71 100644 --- a/homeassistant/components/timer/reproduce_state.py +++ b/homeassistant/components/timer/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Timer state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -24,7 +24,11 @@ VALID_STATES = {STATUS_IDLE, STATUS_ACTIVE, STATUS_PAUSED} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -62,9 +66,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Timer states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py index 0c9791b00ce..f3e7c221da1 100644 --- a/homeassistant/components/tuya/scene.py +++ b/homeassistant/components/tuya/scene.py @@ -1,4 +1,6 @@ """Support for the Tuya scenes.""" +from typing import Any + from homeassistant.components.scene import DOMAIN, Scene from . import DATA_TUYA, TuyaDevice @@ -29,6 +31,6 @@ class TuyaScene(TuyaDevice, Scene): super().__init__(tuya) self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self.tuya.activate() diff --git a/homeassistant/components/vacuum/reproduce_state.py b/homeassistant/components/vacuum/reproduce_state.py index 485ffef0c9f..48aa9615f1e 100644 --- a/homeassistant/components/vacuum/reproduce_state.py +++ b/homeassistant/components/vacuum/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Vacuum state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -41,7 +41,11 @@ VALID_STATES_STATE = { async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -50,7 +54,7 @@ async def _async_reproduce_state( _LOGGER.warning("Unable to find entity %s", state.entity_id) return - if state.state not in VALID_STATES_TOGGLE and state.state not in VALID_STATES_STATE: + if not (state.state in VALID_STATES_TOGGLE or state.state in VALID_STATES_STATE): _LOGGER.warning( "Invalid state specified for %s: %s", state.entity_id, state.state ) @@ -72,7 +76,7 @@ async def _async_reproduce_state( service = SERVICE_TURN_OFF elif state.state == STATE_CLEANING: service = SERVICE_START - elif state.state == STATE_DOCKED or state.state == STATE_RETURNING: + elif state.state in [STATE_DOCKED, STATE_RETURNING]: service = SERVICE_RETURN_TO_BASE elif state.state == STATE_IDLE: service = SERVICE_STOP @@ -92,10 +96,19 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Vacuum states.""" # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index f93e73e72c7..96ff0558fff 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,4 +1,6 @@ """Support for VELUX scenes.""" +from typing import Any + from homeassistant.components.scene import Scene from . import _LOGGER, DATA_VELUX @@ -6,9 +8,7 @@ from . import _LOGGER, DATA_VELUX async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the scenes for Velux platform.""" - entities = [] - for scene in hass.data[DATA_VELUX].pyvlx.scenes: - entities.append(VeluxScene(scene)) + entities = [VeluxScene(scene) for scene in hass.data[DATA_VELUX].pyvlx.scenes] async_add_entities(entities) @@ -25,6 +25,6 @@ class VeluxScene(Scene): """Return the name of the scene.""" return self.scene.name - async def async_activate(self): + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" await self.scene.run(wait_for_completion=False) diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 7d09e248893..2f3069f5332 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,6 +1,6 @@ """Support for Vera scenes.""" import logging -from typing import Callable, List +from typing import Any, Callable, List from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry @@ -46,7 +46,7 @@ class VeraScene(Scene): """Update the scene status.""" self.vera_scene.refresh() - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self.vera_scene.activate() diff --git a/homeassistant/components/water_heater/reproduce_state.py b/homeassistant/components/water_heater/reproduce_state.py index 2038b4c237b..77cdba93f96 100644 --- a/homeassistant/components/water_heater/reproduce_state.py +++ b/homeassistant/components/water_heater/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an Water heater state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -44,7 +44,11 @@ VALID_STATES = { async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -117,9 +121,18 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce Water heater states.""" await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) diff --git a/homeassistant/components/wink/scene.py b/homeassistant/components/wink/scene.py index ff083598b2e..753e2c24278 100644 --- a/homeassistant/components/wink/scene.py +++ b/homeassistant/components/wink/scene.py @@ -1,5 +1,6 @@ """Support for Wink scenes.""" import logging +from typing import Any import pywink @@ -31,6 +32,6 @@ class WinkScene(WinkDevice, Scene): """Call when entity is added to hass.""" self.hass.data[DOMAIN]["entities"]["scene"].append(self) - def activate(self): + def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self.wink.activate() diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 60e6acc8797..87112cd9133 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -4,7 +4,7 @@ from collections import defaultdict import datetime as dt import logging from types import ModuleType, TracebackType -from typing import Dict, Iterable, List, Optional, Type, Union +from typing import Any, Dict, Iterable, List, Optional, Type, Union from homeassistant.components.sun import STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON from homeassistant.const import ( @@ -69,8 +69,9 @@ def get_changed_since( async def async_reproduce_state( hass: HomeAssistantType, states: Union[State, Iterable[State]], - blocking: bool = False, + *, context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a list of states on multiple domains.""" if isinstance(states, State): @@ -97,7 +98,7 @@ async def async_reproduce_state( return await platform.async_reproduce_states( # type: ignore - hass, states_by_domain, context=context + hass, states_by_domain, context=context, reproduce_options=reproduce_options ) if to_call: diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index 871142a5b00..bb2ee7aee96 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -1,7 +1,7 @@ """Reproduce an NEW_NAME state.""" import asyncio import logging -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from homeassistant.const import ( ATTR_ENTITY_ID, @@ -22,7 +22,11 @@ VALID_STATES = {STATE_ON, STATE_OFF} async def _async_reproduce_state( - hass: HomeAssistantType, state: State, context: Optional[Context] = None + hass: HomeAssistantType, + state: State, + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -63,16 +67,25 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None + hass: HomeAssistantType, + states: Iterable[State], + *, + context: Optional[Context] = None, + reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce NEW_NAME states.""" # TODO pick one and remove other one # Reproduce states in parallel. await asyncio.gather( - *(_async_reproduce_state(hass, state, context) for state in states) + *( + _async_reproduce_state( + hass, state, context=context, reproduce_options=reproduce_options + ) + for state in states + ) ) # Alternative: Reproduce states in sequence # for state in states: - # await _async_reproduce_state(hass, state, context) + # await _async_reproduce_state(hass, state, context=context, reproduce_options=reproduce_options) diff --git a/tests/components/alarm_control_panel/test_reproduce_state.py b/tests/components/alarm_control_panel/test_reproduce_state.py index 61b0e3ccd30..686b281bff8 100644 --- a/tests/components/alarm_control_panel/test_reproduce_state.py +++ b/tests/components/alarm_control_panel/test_reproduce_state.py @@ -70,8 +70,7 @@ async def test_reproducing_states(hass, caplog): State("alarm_control_panel.entity_armed_night", STATE_ALARM_ARMED_NIGHT), State("alarm_control_panel.entity_disarmed", STATE_ALARM_DISARMED), State("alarm_control_panel.entity_triggered", STATE_ALARM_TRIGGERED), - ], - blocking=True, + ] ) assert len(arm_away_calls) == 0 @@ -83,7 +82,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("alarm_control_panel.entity_triggered", "not_supported")], blocking=True + [State("alarm_control_panel.entity_triggered", "not_supported")] ) assert "not_supported" in caplog.text @@ -109,8 +108,7 @@ async def test_reproducing_states(hass, caplog): State("alarm_control_panel.entity_triggered", STATE_ALARM_DISARMED), # Should not raise State("alarm_control_panel.non_existing", "on"), - ], - blocking=True, + ] ) assert len(arm_away_calls) == 1 diff --git a/tests/components/automation/test_reproduce_state.py b/tests/components/automation/test_reproduce_state.py index 4f3fd735fc5..c00378aa369 100644 --- a/tests/components/automation/test_reproduce_state.py +++ b/tests/components/automation/test_reproduce_state.py @@ -14,8 +14,7 @@ async def test_reproducing_states(hass, caplog): # These calls should do nothing as entities already in desired state await hass.helpers.state.async_reproduce_state( - [State("automation.entity_off", "off"), State("automation.entity_on", "on")], - blocking=True, + [State("automation.entity_off", "off"), State("automation.entity_on", "on")] ) assert len(turn_on_calls) == 0 @@ -23,7 +22,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("automation.entity_off", "not_supported")], blocking=True + [State("automation.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -37,8 +36,7 @@ async def test_reproducing_states(hass, caplog): State("automation.entity_off", "on"), # Should not raise State("automation.non_existing", "on"), - ], - blocking=True, + ] ) assert len(turn_on_calls) == 1 diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py index df0b6314d63..bdcf0441b9f 100644 --- a/tests/components/climate/test_reproduce_state.py +++ b/tests/components/climate/test_reproduce_state.py @@ -82,7 +82,9 @@ async def test_state_with_context(hass): context = Context() - await async_reproduce_states(hass, [State(ENTITY_1, HVAC_MODE_HEAT)], context) + await async_reproduce_states( + hass, [State(ENTITY_1, HVAC_MODE_HEAT)], context=context + ) await hass.async_block_till_done() diff --git a/tests/components/counter/test_reproduce_state.py b/tests/components/counter/test_reproduce_state.py index aa2c5ddbd9a..7b7816f8272 100644 --- a/tests/components/counter/test_reproduce_state.py +++ b/tests/components/counter/test_reproduce_state.py @@ -24,15 +24,14 @@ async def test_reproducing_states(hass, caplog): "8", {"initial": 12, "minimum": 5, "maximum": 15, "step": 3}, ), - ], - blocking=True, + ] ) assert len(configure_calls) == 0 # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("counter.entity", "not_supported")], blocking=True + [State("counter.entity", "not_supported")] ) assert "not_supported" in caplog.text @@ -49,8 +48,7 @@ async def test_reproducing_states(hass, caplog): ), # Should not raise State("counter.non_existing", "6"), - ], - blocking=True, + ] ) valid_calls = [ diff --git a/tests/components/cover/test_reproduce_state.py b/tests/components/cover/test_reproduce_state.py index 2e2d0f63467..aec4c8c2324 100644 --- a/tests/components/cover/test_reproduce_state.py +++ b/tests/components/cover/test_reproduce_state.py @@ -93,8 +93,7 @@ async def test_reproducing_states(hass, caplog): STATE_OPEN, {ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 100}, ), - ], - blocking=True, + ] ) assert len(close_calls) == 0 @@ -106,7 +105,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("cover.entity_close", "not_supported")], blocking=True + [State("cover.entity_close", "not_supported")] ) assert "not_supported" in caplog.text @@ -145,7 +144,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("cover.non_existing", "on"), ], - blocking=True, ) valid_close_calls = [ diff --git a/tests/components/fan/test_reproduce_state.py b/tests/components/fan/test_reproduce_state.py index 0dcd38580b8..149332b3fa8 100644 --- a/tests/components/fan/test_reproduce_state.py +++ b/tests/components/fan/test_reproduce_state.py @@ -27,7 +27,6 @@ async def test_reproducing_states(hass, caplog): State("fan.entity_oscillating", "on", {"oscillating": True}), State("fan.entity_direction", "on", {"direction": "forward"}), ], - blocking=True, ) assert len(turn_on_calls) == 0 @@ -38,7 +37,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("fan.entity_off", "not_supported")], blocking=True + [State("fan.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -59,7 +58,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("fan.non_existing", "on"), ], - blocking=True, ) assert len(turn_on_calls) == 1 diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py index 5cc1c862cd3..58bbc94876e 100644 --- a/tests/components/group/test_reproduce_state.py +++ b/tests/components/group/test_reproduce_state.py @@ -39,7 +39,7 @@ async def test_reproduce_group(hass): state = State("group.test", "on") - await async_reproduce_states(hass, [state], context) + await async_reproduce_states(hass, [state], context=context) fun.assert_called_once_with( hass, @@ -48,6 +48,6 @@ async def test_reproduce_group(hass): clone_state(state, "light.test2"), clone_state(state, "switch.test1"), ], - blocking=True, context=context, + reproduce_options=None, ) diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 127d7044be9..1371db87b3d 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -58,6 +58,24 @@ async def test_apply_service(hass): assert state.state == "on" assert state.attributes["brightness"] == 50 + turn_on_calls = async_mock_service(hass, "light", "turn_on") + assert await hass.services.async_call( + "scene", + "apply", + { + "transition": 42, + "entities": {"light.bed_light": {"state": "on", "brightness": 50}}, + }, + blocking=True, + ) + + assert len(turn_on_calls) == 1 + assert turn_on_calls[0].domain == "light" + assert turn_on_calls[0].service == "turn_on" + assert turn_on_calls[0].data.get("transition") == 42 + assert turn_on_calls[0].data.get("entity_id") == "light.bed_light" + assert turn_on_calls[0].data.get("brightness") == 50 + async def test_create_service(hass, caplog): """Test the create service.""" diff --git a/tests/components/input_boolean/test_reproduce_state.py b/tests/components/input_boolean/test_reproduce_state.py index 7ce4f4c1fd1..3ee5bfb5da9 100644 --- a/tests/components/input_boolean/test_reproduce_state.py +++ b/tests/components/input_boolean/test_reproduce_state.py @@ -22,7 +22,6 @@ async def test_reproducing_states(hass): # Should not raise State("input_boolean.non_existing", "on"), ], - blocking=True, ) assert hass.states.get("input_boolean.initial_off").state == "on" assert hass.states.get("input_boolean.initial_on").state == "off" @@ -34,7 +33,6 @@ async def test_reproducing_states(hass): # Set to state it already is. State("input_boolean.initial_off", "on"), ], - blocking=True, ) assert hass.states.get("input_boolean.initial_on").state == "off" diff --git a/tests/components/input_datetime/test_reproduce_state.py b/tests/components/input_datetime/test_reproduce_state.py index 428b5189117..87d737dfb4f 100644 --- a/tests/components/input_datetime/test_reproduce_state.py +++ b/tests/components/input_datetime/test_reproduce_state.py @@ -29,7 +29,6 @@ async def test_reproducing_states(hass, caplog): State("input_datetime.entity_time", "01:20:00"), State("input_datetime.entity_date", "2010-10-10"), ], - blocking=True, ) assert len(datetime_calls) == 0 @@ -42,7 +41,6 @@ async def test_reproducing_states(hass, caplog): State("input_datetime.entity_datetime", "not:valid:time"), State("input_datetime.entity_datetime", "1234-56-78 90:12:34"), ], - blocking=True, ) assert "not_supported" in caplog.text @@ -60,7 +58,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_datetime.non_existing", "2010-10-10 01:20:00"), ], - blocking=True, ) valid_calls = [ diff --git a/tests/components/input_number/test_reproduce_state.py b/tests/components/input_number/test_reproduce_state.py index 37ab83f3204..38a777732ad 100644 --- a/tests/components/input_number/test_reproduce_state.py +++ b/tests/components/input_number/test_reproduce_state.py @@ -26,7 +26,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_number.non_existing", "234"), ], - blocking=True, ) assert hass.states.get("input_number.test_number").state == VALID_NUMBER1 @@ -38,14 +37,13 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_number.non_existing", "234"), ], - blocking=True, ) assert hass.states.get("input_number.test_number").state == VALID_NUMBER2 # Test setting state to number out of range await hass.helpers.state.async_reproduce_state( - [State("input_number.test_number", "150")], blocking=True + [State("input_number.test_number", "150")] ) # The entity states should be unchanged after trying to set them to out-of-range number @@ -58,5 +56,4 @@ async def test_reproducing_states(hass, caplog): # Set to state it already is. State("input_number.test_number", VALID_NUMBER2), ], - blocking=True, ) diff --git a/tests/components/input_select/test_reproduce_state.py b/tests/components/input_select/test_reproduce_state.py index ed1f9f45e43..c4cfbea268d 100644 --- a/tests/components/input_select/test_reproduce_state.py +++ b/tests/components/input_select/test_reproduce_state.py @@ -35,7 +35,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_select.non_existing", VALID_OPTION1), ], - blocking=True, ) # Test that entity is in desired state @@ -48,23 +47,20 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_select.non_existing", VALID_OPTION3), ], - blocking=True, ) # Test that we got the desired result assert hass.states.get(ENTITY).state == VALID_OPTION3 # Test setting state to invalid state - await hass.helpers.state.async_reproduce_state( - [State(ENTITY, INVALID_OPTION)], blocking=True - ) + await hass.helpers.state.async_reproduce_state([State(ENTITY, INVALID_OPTION)]) # The entity state should be unchanged assert hass.states.get(ENTITY).state == VALID_OPTION3 # Test setting a different option set await hass.helpers.state.async_reproduce_state( - [State(ENTITY, VALID_OPTION5, {"options": VALID_OPTION_SET2})], blocking=True + [State(ENTITY, VALID_OPTION5, {"options": VALID_OPTION_SET2})] ) # These should fail if options weren't changed to VALID_OPTION_SET2 diff --git a/tests/components/input_text/test_reproduce_state.py b/tests/components/input_text/test_reproduce_state.py index fd75948d461..01117a28f53 100644 --- a/tests/components/input_text/test_reproduce_state.py +++ b/tests/components/input_text/test_reproduce_state.py @@ -29,7 +29,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_text.non_existing", VALID_TEXT1), ], - blocking=True, ) # Test that entity is in desired state @@ -42,7 +41,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("input_text.non_existing", VALID_TEXT2), ], - blocking=True, ) # Test that the state was changed @@ -50,7 +48,7 @@ async def test_reproducing_states(hass, caplog): # Test setting state to invalid state (length too long) await hass.helpers.state.async_reproduce_state( - [State("input_text.test_text", INVALID_TEXT1)], blocking=True + [State("input_text.test_text", INVALID_TEXT1)] ) # The entity state should be unchanged @@ -58,7 +56,7 @@ async def test_reproducing_states(hass, caplog): # Test setting state to invalid state (length too short) await hass.helpers.state.async_reproduce_state( - [State("input_text.test_text", INVALID_TEXT2)], blocking=True + [State("input_text.test_text", INVALID_TEXT2)] ) # The entity state should be unchanged diff --git a/tests/components/light/test_reproduce_state.py b/tests/components/light/test_reproduce_state.py index 1c40f352ff0..e96f4ff4528 100644 --- a/tests/components/light/test_reproduce_state.py +++ b/tests/components/light/test_reproduce_state.py @@ -53,8 +53,7 @@ async def test_reproducing_states(hass, caplog): State("light.entity_profile", "on", VALID_PROFILE), State("light.entity_rgb", "on", VALID_RGB_COLOR), State("light.entity_xy", "on", VALID_XY_COLOR), - ], - blocking=True, + ] ) assert len(turn_on_calls) == 0 @@ -62,7 +61,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("light.entity_off", "not_supported")], blocking=True + [State("light.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -86,7 +85,6 @@ async def test_reproducing_states(hass, caplog): State("light.entity_profile", "on", VALID_RGB_COLOR), State("light.entity_rgb", "on", VALID_XY_COLOR), ], - blocking=True, ) assert len(turn_on_calls) == 12 @@ -163,7 +161,7 @@ async def test_deprecation_warning(hass, caplog): hass.states.async_set("light.entity_off", "off", {}) turn_on_calls = async_mock_service(hass, "light", "turn_on") await hass.helpers.state.async_reproduce_state( - [State("light.entity_off", "on", {"brightness_pct": 80})], blocking=True + [State("light.entity_off", "on", {"brightness_pct": 80})] ) assert len(turn_on_calls) == 1 assert DEPRECATION_WARNING % ["brightness_pct"] in caplog.text diff --git a/tests/components/lock/test_reproduce_state.py b/tests/components/lock/test_reproduce_state.py index a9b61fa1219..8c08e9f6b10 100644 --- a/tests/components/lock/test_reproduce_state.py +++ b/tests/components/lock/test_reproduce_state.py @@ -18,7 +18,6 @@ async def test_reproducing_states(hass, caplog): State("lock.entity_locked", "locked"), State("lock.entity_unlocked", "unlocked", {}), ], - blocking=True, ) assert len(lock_calls) == 0 @@ -26,7 +25,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("lock.entity_locked", "not_supported")], blocking=True + [State("lock.entity_locked", "not_supported")] ) assert "not_supported" in caplog.text @@ -41,7 +40,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("lock.non_existing", "on"), ], - blocking=True, ) assert len(lock_calls) == 1 diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py index cbc5684f5d5..ee2c9be377f 100644 --- a/tests/components/media_player/test_reproduce_state.py +++ b/tests/components/media_player/test_reproduce_state.py @@ -115,7 +115,7 @@ async def test_state_with_context(hass): context = Context() - await async_reproduce_states(hass, [State(ENTITY_1, "on")], context) + await async_reproduce_states(hass, [State(ENTITY_1, "on")], context=context) await hass.async_block_till_done() diff --git a/tests/components/remote/test_reproduce_state.py b/tests/components/remote/test_reproduce_state.py index ee1574d1741..8795cadb4de 100644 --- a/tests/components/remote/test_reproduce_state.py +++ b/tests/components/remote/test_reproduce_state.py @@ -15,7 +15,6 @@ async def test_reproducing_states(hass, caplog): # These calls should do nothing as entities already in desired state await hass.helpers.state.async_reproduce_state( [State("remote.entity_off", "off"), State("remote.entity_on", "on")], - blocking=True, ) assert len(turn_on_calls) == 0 @@ -23,7 +22,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("remote.entity_off", "not_supported")], blocking=True + [State("remote.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -38,7 +37,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("remote.non_existing", "on"), ], - blocking=True, ) assert len(turn_on_calls) == 1 diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index fd09d378b46..483ab17faa6 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -6,7 +6,7 @@ from homeassistant.components import light, scene from homeassistant.setup import async_setup_component, setup_component from homeassistant.util.yaml import loader as yaml_loader -from tests.common import get_test_home_assistant +from tests.common import get_test_home_assistant, mock_service from tests.components.light import common as common_light from tests.components.scene import common @@ -127,7 +127,19 @@ class TestScene(unittest.TestCase): assert self.light_1.is_on assert self.light_2.is_on - assert 100 == self.light_2.last_call("turn_on")[1].get("brightness") + assert self.light_2.last_call("turn_on")[1].get("brightness") == 100 + + turn_on_calls = mock_service(self.hass, "light", "turn_on") + + self.hass.services.call( + scene.DOMAIN, "turn_on", {"transition": 42, "entity_id": "scene.test"} + ) + self.hass.block_till_done() + + assert len(turn_on_calls) == 1 + assert turn_on_calls[0].domain == "light" + assert turn_on_calls[0].service == "turn_on" + assert turn_on_calls[0].data.get("transition") == 42 async def test_services_registered(hass): diff --git a/tests/components/switch/test_reproduce_state.py b/tests/components/switch/test_reproduce_state.py index 4b6db84bfdd..65de4b36c59 100644 --- a/tests/components/switch/test_reproduce_state.py +++ b/tests/components/switch/test_reproduce_state.py @@ -15,7 +15,6 @@ async def test_reproducing_states(hass, caplog): # These calls should do nothing as entities already in desired state await hass.helpers.state.async_reproduce_state( [State("switch.entity_off", "off"), State("switch.entity_on", "on", {})], - blocking=True, ) assert len(turn_on_calls) == 0 @@ -23,7 +22,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("switch.entity_off", "not_supported")], blocking=True + [State("switch.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -37,8 +36,7 @@ async def test_reproducing_states(hass, caplog): State("switch.entity_off", "on", {}), # Should not raise State("switch.non_existing", "on"), - ], - blocking=True, + ] ) assert len(turn_on_calls) == 1 diff --git a/tests/components/timer/test_reproduce_state.py b/tests/components/timer/test_reproduce_state.py index 80205a40f5d..8ee8a86c5cc 100644 --- a/tests/components/timer/test_reproduce_state.py +++ b/tests/components/timer/test_reproduce_state.py @@ -36,7 +36,6 @@ async def test_reproducing_states(hass, caplog): "timer.entity_active_attr", STATUS_ACTIVE, {ATTR_DURATION: "00:01:00"} ), ], - blocking=True, ) assert len(start_calls) == 0 @@ -45,7 +44,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("timer.entity_idle", "not_supported")], blocking=True + [State("timer.entity_idle", "not_supported")] ) assert "not_supported" in caplog.text @@ -63,7 +62,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("timer.non_existing", "on"), ], - blocking=True, ) valid_start_calls = [ diff --git a/tests/components/vacuum/test_reproduce_state.py b/tests/components/vacuum/test_reproduce_state.py index d5a7051e6a6..5edc3a924e9 100644 --- a/tests/components/vacuum/test_reproduce_state.py +++ b/tests/components/vacuum/test_reproduce_state.py @@ -59,7 +59,6 @@ async def test_reproducing_states(hass, caplog): State("vacuum.entity_returning", STATE_RETURNING), State("vacuum.entity_paused", STATE_PAUSED), ], - blocking=True, ) assert len(turn_on_calls) == 0 @@ -72,7 +71,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("vacuum.entity_off", "not_supported")], blocking=True + [State("vacuum.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -98,7 +97,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("vacuum.non_existing", STATE_ON), ], - blocking=True, ) assert len(turn_on_calls) == 1 diff --git a/tests/components/water_heater/test_reproduce_state.py b/tests/components/water_heater/test_reproduce_state.py index 93e7deb37ee..d1719986fc9 100644 --- a/tests/components/water_heater/test_reproduce_state.py +++ b/tests/components/water_heater/test_reproduce_state.py @@ -45,8 +45,7 @@ async def test_reproducing_states(hass, caplog): STATE_ECO, {ATTR_AWAY_MODE: True, ATTR_TEMPERATURE: 45}, ), - ], - blocking=True, + ] ) assert len(turn_on_calls) == 0 @@ -57,7 +56,7 @@ async def test_reproducing_states(hass, caplog): # Test invalid state is handled await hass.helpers.state.async_reproduce_state( - [State("water_heater.entity_off", "not_supported")], blocking=True + [State("water_heater.entity_off", "not_supported")] ) assert "not_supported" in caplog.text @@ -82,7 +81,6 @@ async def test_reproducing_states(hass, caplog): # Should not raise State("water_heater.non_existing", "on"), ], - blocking=True, ) assert len(turn_on_calls) == 1 diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 26043c37093..d8202b88b46 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -68,17 +68,16 @@ async def test_call_to_component(hass): context = "dummy_context" await state.async_reproduce_state( - hass, - [state_media_player, state_climate], - blocking=True, - context=context, + hass, [state_media_player, state_climate], context=context, ) media_player_fun.assert_called_once_with( - hass, [state_media_player], context=context + hass, [state_media_player], context=context, reproduce_options=None ) - climate_fun.assert_called_once_with(hass, [state_climate], context=context) + climate_fun.assert_called_once_with( + hass, [state_climate], context=context, reproduce_options=None + ) async def test_get_changed_since(hass):