Reproduce states by letting each component opt in on handling state recovery itself (#18700)

* Move group to it's own setup

* Let each component to handle restore of state

* Move constants for climate into const.py

For now import all into __init__.py to keep backword compat

* Move media plyaer constants to const.py file

For now import all constants into __init__.py to keep
backword compatibility

* Move media player to it's own file

* Move climate to it's own file

* Remove ecobee service from common components

BREAKING CHANGE

* Add tests for climate

* Add test for media_player

* Make sure we clone timestamps of state

* Add tests for groups

* Remove old tests for media player, it's handled by other tests

* Add tests for calls to component functions

* Add docstring for climate const

* Add docstring for media_player const

* Explicitly import constants in climate

* Explicitly import constants in media_player

* Add period to climate const

* Add period to media_player const

* Fix some lint errors in climate

* Fix some lint errors in media_player

* Fix lint warnings on climate tests

* Fix lint warnings on group tests

* Fix lint warnings on media_player tests

* Fix lint warnings on state tests

* Adjust indent for state tests
This commit is contained in:
Joakim Plate 2019-02-06 02:25:27 +01:00 committed by Paulus Schoutsen
parent c76a61ad16
commit 3bb5caabe2
13 changed files with 846 additions and 228 deletions

View file

@ -22,25 +22,46 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS)
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_LIST,
ATTR_FAN_MODE,
ATTR_HOLD_MODE,
ATTR_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MIN_TEMP,
ATTR_OPERATION_LIST,
ATTR_OPERATION_MODE,
ATTR_SWING_LIST,
ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_STEP,
DOMAIN,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_HUMIDITY,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
)
from .reproduce_state import async_reproduce_states # noqa
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
DEFAULT_MIN_HUMITIDY = 30
DEFAULT_MAX_HUMIDITY = 99
DOMAIN = 'climate'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=60)
SERVICE_SET_AWAY_MODE = 'set_away_mode'
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
SERVICE_SET_TEMPERATURE = 'set_temperature'
SERVICE_SET_FAN_MODE = 'set_fan_mode'
SERVICE_SET_HOLD_MODE = 'set_hold_mode'
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
SERVICE_SET_SWING_MODE = 'set_swing_mode'
SERVICE_SET_HUMIDITY = 'set_humidity'
STATE_HEAT = 'heat'
STATE_COOL = 'cool'
STATE_IDLE = 'idle'
@ -64,26 +85,6 @@ SUPPORT_AWAY_MODE = 1024
SUPPORT_AUX_HEAT = 2048
SUPPORT_ON_OFF = 4096
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_TEMP = 'min_temp'
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
ATTR_TARGET_TEMP_STEP = 'target_temp_step'
ATTR_AWAY_MODE = 'away_mode'
ATTR_AUX_HEAT = 'aux_heat'
ATTR_FAN_MODE = 'fan_mode'
ATTR_FAN_LIST = 'fan_list'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_HUMIDITY = 'humidity'
ATTR_MAX_HUMIDITY = 'max_humidity'
ATTR_MIN_HUMIDITY = 'min_humidity'
ATTR_HOLD_MODE = 'hold_mode'
ATTR_OPERATION_MODE = 'operation_mode'
ATTR_OPERATION_LIST = 'operation_list'
ATTR_SWING_MODE = 'swing_mode'
ATTR_SWING_LIST = 'swing_list'
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,

View file

@ -0,0 +1,32 @@
"""Proides the constants needed for component."""
ATTR_AUX_HEAT = 'aux_heat'
ATTR_AWAY_MODE = 'away_mode'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_FAN_LIST = 'fan_list'
ATTR_FAN_MODE = 'fan_mode'
ATTR_HOLD_MODE = 'hold_mode'
ATTR_HUMIDITY = 'humidity'
ATTR_MAX_HUMIDITY = 'max_humidity'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_HUMIDITY = 'min_humidity'
ATTR_MIN_TEMP = 'min_temp'
ATTR_OPERATION_LIST = 'operation_list'
ATTR_OPERATION_MODE = 'operation_mode'
ATTR_SWING_LIST = 'swing_list'
ATTR_SWING_MODE = 'swing_mode'
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
ATTR_TARGET_TEMP_STEP = 'target_temp_step'
DOMAIN = 'climate'
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
SERVICE_SET_AWAY_MODE = 'set_away_mode'
SERVICE_SET_FAN_MODE = 'set_fan_mode'
SERVICE_SET_HOLD_MODE = 'set_hold_mode'
SERVICE_SET_HUMIDITY = 'set_humidity'
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
SERVICE_SET_SWING_MODE = 'set_swing_mode'
SERVICE_SET_TEMPERATURE = 'set_temperature'

View file

@ -0,0 +1,91 @@
"""Module that groups code required to handle state restore for component."""
import asyncio
from typing import Iterable, Optional
from homeassistant.const import (
ATTR_TEMPERATURE, SERVICE_TURN_OFF,
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_HOLD_MODE,
ATTR_OPERATION_MODE,
ATTR_SWING_MODE,
ATTR_HUMIDITY,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_TEMPERATURE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_HUMIDITY,
DOMAIN,
)
async def _async_reproduce_states(hass: HomeAssistantType,
state: State,
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
async def call_service(service: str, keys: Iterable):
"""Call service with set of attributes given."""
data = {}
data['entity_id'] = state.entity_id
for key in keys:
if key in state.attributes:
data[key] = state.attributes[key]
await hass.services.async_call(
DOMAIN, service, data,
blocking=True, context=context)
if state.state == STATE_ON:
await call_service(SERVICE_TURN_ON, [])
elif state.state == STATE_OFF:
await call_service(SERVICE_TURN_OFF, [])
if ATTR_AUX_HEAT in state.attributes:
await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT])
if ATTR_AWAY_MODE in state.attributes:
await call_service(SERVICE_SET_AWAY_MODE, [ATTR_AWAY_MODE])
if (ATTR_TEMPERATURE in state.attributes) or \
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
(ATTR_TARGET_TEMP_LOW in state.attributes):
await call_service(SERVICE_SET_TEMPERATURE,
[ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW])
if ATTR_HOLD_MODE in state.attributes:
await call_service(SERVICE_SET_HOLD_MODE,
[ATTR_HOLD_MODE])
if ATTR_OPERATION_MODE in state.attributes:
await call_service(SERVICE_SET_OPERATION_MODE,
[ATTR_OPERATION_MODE])
if ATTR_SWING_MODE in state.attributes:
await call_service(SERVICE_SET_SWING_MODE,
[ATTR_SWING_MODE])
if ATTR_HUMIDITY in state.attributes:
await call_service(SERVICE_SET_HUMIDITY,
[ATTR_HUMIDITY])
@bind_hass
async def async_reproduce_states(hass: HomeAssistantType,
states: Iterable[State],
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
await asyncio.gather(*[
_async_reproduce_states(hass, state, context)
for state in states])

View file

@ -23,6 +23,8 @@ from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe
from .reproduce_state import async_reproduce_states # noqa
DOMAIN = 'group'
ENTITY_ID_FORMAT = DOMAIN + '.{}'

View file

@ -0,0 +1,28 @@
"""Module that groups code required to handle state restore for component."""
from typing import Iterable, Optional
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
@bind_hass
async def async_reproduce_states(hass: HomeAssistantType,
states: Iterable[State],
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
from . import get_entity_ids
from homeassistant.helpers.state import async_reproduce_state
states_copy = []
for state in states:
members = get_entity_ids(hass, state.entity_id)
for member in members:
states_copy.append(
State(member,
state.state,
state.attributes,
last_changed=state.last_changed,
last_updated=state.last_updated,
context=state.context))
await async_reproduce_state(hass, states_copy, blocking=True,
context=context)

View file

@ -36,10 +36,44 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import bind_hass
from .const import (
ATTR_APP_ID,
ATTR_APP_NAME,
ATTR_INPUT_SOURCE,
ATTR_INPUT_SOURCE_LIST,
ATTR_MEDIA_ALBUM_ARTIST,
ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_ARTIST,
ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_DURATION,
ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_EPISODE,
ATTR_MEDIA_PLAYLIST,
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
ATTR_MEDIA_SEASON,
ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_SERIES_TITLE,
ATTR_MEDIA_SHUFFLE,
ATTR_MEDIA_TITLE,
ATTR_MEDIA_TRACK,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
ATTR_SOUND_MODE,
ATTR_SOUND_MODE_LIST,
DOMAIN,
SERVICE_CLEAR_PLAYLIST,
SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOUND_MODE,
SERVICE_SELECT_SOURCE,
)
from .reproduce_state import async_reproduce_states # noqa
_LOGGER = logging.getLogger(__name__)
_RND = SystemRandom()
DOMAIN = 'media_player'
DEPENDENCIES = ['http']
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -55,38 +89,6 @@ ENTITY_IMAGE_CACHE = {
CACHE_MAXSIZE: 16
}
SERVICE_PLAY_MEDIA = 'play_media'
SERVICE_SELECT_SOURCE = 'select_source'
SERVICE_SELECT_SOUND_MODE = 'select_sound_mode'
SERVICE_CLEAR_PLAYLIST = 'clear_playlist'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
ATTR_MEDIA_SEEK_POSITION = 'seek_position'
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
ATTR_MEDIA_CONTENT_TYPE = 'media_content_type'
ATTR_MEDIA_DURATION = 'media_duration'
ATTR_MEDIA_POSITION = 'media_position'
ATTR_MEDIA_POSITION_UPDATED_AT = 'media_position_updated_at'
ATTR_MEDIA_TITLE = 'media_title'
ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_ALBUM_NAME = 'media_album_name'
ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist'
ATTR_MEDIA_TRACK = 'media_track'
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_EPISODE = 'media_episode'
ATTR_MEDIA_CHANNEL = 'media_channel'
ATTR_MEDIA_PLAYLIST = 'media_playlist'
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
ATTR_INPUT_SOURCE = 'source'
ATTR_INPUT_SOURCE_LIST = 'source_list'
ATTR_SOUND_MODE = 'sound_mode'
ATTR_SOUND_MODE_LIST = 'sound_mode_list'
ATTR_MEDIA_ENQUEUE = 'enqueue'
ATTR_MEDIA_SHUFFLE = 'shuffle'
MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow'
MEDIA_TYPE_MOVIE = 'movie'

View file

@ -0,0 +1,35 @@
"""Proides the constants needed for component."""
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
ATTR_INPUT_SOURCE = 'source'
ATTR_INPUT_SOURCE_LIST = 'source_list'
ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist'
ATTR_MEDIA_ALBUM_NAME = 'media_album_name'
ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_CHANNEL = 'media_channel'
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
ATTR_MEDIA_CONTENT_TYPE = 'media_content_type'
ATTR_MEDIA_DURATION = 'media_duration'
ATTR_MEDIA_ENQUEUE = 'enqueue'
ATTR_MEDIA_EPISODE = 'media_episode'
ATTR_MEDIA_PLAYLIST = 'media_playlist'
ATTR_MEDIA_POSITION = 'media_position'
ATTR_MEDIA_POSITION_UPDATED_AT = 'media_position_updated_at'
ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_SEEK_POSITION = 'seek_position'
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_SHUFFLE = 'shuffle'
ATTR_MEDIA_TITLE = 'media_title'
ATTR_MEDIA_TRACK = 'media_track'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
ATTR_SOUND_MODE = 'sound_mode'
ATTR_SOUND_MODE_LIST = 'sound_mode_list'
DOMAIN = 'media_player'
SERVICE_CLEAR_PLAYLIST = 'clear_playlist'
SERVICE_PLAY_MEDIA = 'play_media'
SERVICE_SELECT_SOUND_MODE = 'select_sound_mode'
SERVICE_SELECT_SOURCE = 'select_source'

View file

@ -0,0 +1,87 @@
"""Module that groups code required to handle state restore for component."""
import asyncio
from typing import Iterable, Optional
from homeassistant.const import (
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK,
SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED,
STATE_PLAYING)
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from .const import (
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_SEEK_POSITION,
ATTR_INPUT_SOURCE,
ATTR_SOUND_MODE,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_ENQUEUE,
SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOURCE,
SERVICE_SELECT_SOUND_MODE,
DOMAIN,
)
async def _async_reproduce_states(hass: HomeAssistantType,
state: State,
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
async def call_service(service: str, keys: Iterable):
"""Call service with set of attributes given."""
data = {}
data['entity_id'] = state.entity_id
for key in keys:
if key in state.attributes:
data[key] = state.attributes[key]
await hass.services.async_call(
DOMAIN, service, data,
blocking=True, context=context)
if state.state == STATE_ON:
await call_service(SERVICE_TURN_ON, [])
elif state.state == STATE_OFF:
await call_service(SERVICE_TURN_OFF, [])
elif state.state == STATE_PLAYING:
await call_service(SERVICE_MEDIA_PLAY, [])
elif state.state == STATE_IDLE:
await call_service(SERVICE_MEDIA_STOP, [])
elif state.state == STATE_PAUSED:
await call_service(SERVICE_MEDIA_PAUSE, [])
if ATTR_MEDIA_VOLUME_LEVEL in state.attributes:
await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL])
if ATTR_MEDIA_VOLUME_MUTED in state.attributes:
await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED])
if ATTR_MEDIA_SEEK_POSITION in state.attributes:
await call_service(SERVICE_MEDIA_SEEK, [ATTR_MEDIA_SEEK_POSITION])
if ATTR_INPUT_SOURCE in state.attributes:
await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE])
if ATTR_SOUND_MODE in state.attributes:
await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE])
if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and \
(ATTR_MEDIA_CONTENT_ID in state.attributes):
await call_service(SERVICE_PLAY_MEDIA,
[ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_ENQUEUE])
@bind_hass
async def async_reproduce_states(hass: HomeAssistantType,
states: Iterable[State],
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
await asyncio.gather(*[
_async_reproduce_states(hass, state, context)
for state in states])

View file

@ -10,41 +10,27 @@ from typing import ( # noqa: F401 pylint: disable=unused-import
from homeassistant.loader import bind_hass
import homeassistant.util.dt as dt_util
from homeassistant.components.media_player import (
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE)
from homeassistant.components.notify import (
ATTR_MESSAGE, SERVICE_NOTIFY)
from homeassistant.components.sun import (
STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON)
from homeassistant.components.mysensors.switch import (
ATTR_IR_CODE, SERVICE_SEND_IR_CODE)
from homeassistant.components.climate import (
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE,
SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT, STATE_COOL,
STATE_IDLE)
from homeassistant.components.ecobee.climate import (
ATTR_FAN_MIN_ON_TIME, SERVICE_SET_FAN_MIN_ON_TIME,
ATTR_RESUME_ALL, SERVICE_RESUME_PROGRAM)
from homeassistant.components.cover import (
ATTR_POSITION, ATTR_TILT_POSITION)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_OPTION, ATTR_TEMPERATURE, SERVICE_ALARM_ARM_AWAY,
ATTR_ENTITY_ID, ATTR_OPTION, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER,
SERVICE_LOCK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_STOP,
SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK,
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_OPEN_COVER,
SERVICE_LOCK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK,
SERVICE_OPEN_COVER,
SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED,
STATE_CLOSED, STATE_HOME, STATE_LOCKED, STATE_NOT_HOME, STATE_OFF,
STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN,
STATE_ON, STATE_OPEN, STATE_UNKNOWN,
STATE_UNLOCKED, SERVICE_SELECT_OPTION)
from homeassistant.core import State, DOMAIN as HASS_DOMAIN
from homeassistant.core import (
Context, State, DOMAIN as HASS_DOMAIN)
from homeassistant.util.async_ import run_coroutine_threadsafe
from .typing import HomeAssistantType
@ -55,22 +41,7 @@ GROUP_DOMAIN = 'group'
# Update this dict of lists when new services are added to HA.
# Each item is a service with a list of required attributes.
SERVICE_ATTRIBUTES = {
SERVICE_PLAY_MEDIA: [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID],
SERVICE_MEDIA_SEEK: [ATTR_MEDIA_SEEK_POSITION],
SERVICE_VOLUME_MUTE: [ATTR_MEDIA_VOLUME_MUTED],
SERVICE_VOLUME_SET: [ATTR_MEDIA_VOLUME_LEVEL],
SERVICE_NOTIFY: [ATTR_MESSAGE],
SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE],
SERVICE_SET_FAN_MODE: [ATTR_FAN_MODE],
SERVICE_SET_FAN_MIN_ON_TIME: [ATTR_FAN_MIN_ON_TIME],
SERVICE_RESUME_PROGRAM: [ATTR_RESUME_ALL],
SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE],
SERVICE_SET_HUMIDITY: [ATTR_HUMIDITY],
SERVICE_SET_SWING_MODE: [ATTR_SWING_MODE],
SERVICE_SET_HOLD_MODE: [ATTR_HOLD_MODE],
SERVICE_SET_OPERATION_MODE: [ATTR_OPERATION_MODE],
SERVICE_SET_AUX_HEAT: [ATTR_AUX_HEAT],
SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE],
SERVICE_SEND_IR_CODE: [ATTR_IR_CODE],
SERVICE_SELECT_OPTION: [ATTR_OPTION],
SERVICE_SET_COVER_POSITION: [ATTR_POSITION],
@ -82,9 +53,6 @@ SERVICE_ATTRIBUTES = {
SERVICE_TO_STATE = {
SERVICE_TURN_ON: STATE_ON,
SERVICE_TURN_OFF: STATE_OFF,
SERVICE_MEDIA_PLAY: STATE_PLAYING,
SERVICE_MEDIA_PAUSE: STATE_PAUSED,
SERVICE_MEDIA_STOP: STATE_IDLE,
SERVICE_ALARM_ARM_AWAY: STATE_ALARM_ARMED_AWAY,
SERVICE_ALARM_ARM_HOME: STATE_ALARM_ARMED_HOME,
SERVICE_ALARM_DISARM: STATE_ALARM_DISARMED,
@ -142,14 +110,56 @@ def reproduce_state(hass: HomeAssistantType,
@bind_hass
async def async_reproduce_state(hass: HomeAssistantType,
async def async_reproduce_state(
hass: HomeAssistantType,
states: Union[State, Iterable[State]],
blocking: bool = False) -> None:
"""Reproduce given state."""
blocking: bool = False,
context: Optional[Context] = None) -> None:
"""Reproduce a list of states on multiple domains."""
if isinstance(states, State):
states = [states]
to_call = defaultdict(list) # type: Dict[Tuple[str, str, str], List[str]]
to_call = defaultdict(list) # type: Dict[str, List[State]]
for state in states:
to_call[state.domain].append(state)
async def worker(domain: str, data: List[State]) -> None:
component = getattr(hass.components, domain)
if hasattr(component, 'async_reproduce_states'):
await component.async_reproduce_states(
data,
context=context)
else:
await async_reproduce_state_legacy(
hass,
domain,
data,
blocking=blocking,
context=context)
if to_call:
# run all domains in parallel
await asyncio.gather(*[
worker(domain, data)
for domain, data in to_call.items()
])
@bind_hass
async def async_reproduce_state_legacy(
hass: HomeAssistantType,
domain: str,
states: Iterable[State],
blocking: bool = False,
context: Optional[Context] = None) -> None:
"""Reproduce given state."""
to_call = defaultdict(list) # type: Dict[Tuple[str, str], List[str]]
if domain == GROUP_DOMAIN:
service_domain = HASS_DOMAIN
else:
service_domain = domain
for state in states:
@ -158,11 +168,6 @@ async def async_reproduce_state(hass: HomeAssistantType,
state.entity_id)
continue
if state.domain == GROUP_DOMAIN:
service_domain = HASS_DOMAIN
else:
service_domain = state.domain
domain_services = hass.services.async_services().get(service_domain)
if not domain_services:
@ -189,32 +194,22 @@ async def async_reproduce_state(hass: HomeAssistantType,
# We group service calls for entities by service call
# json used to create a hashable version of dict with maybe lists in it
key = (service_domain, service,
key = (service,
json.dumps(dict(state.attributes), sort_keys=True))
to_call[key].append(state.entity_id)
domain_tasks = {} # type: Dict[str, List[Awaitable[Optional[bool]]]]
for (service_domain, service, service_data), entity_ids in to_call.items():
domain_tasks = [] # type: List[Awaitable[Optional[bool]]]
for (service, service_data), entity_ids in to_call.items():
data = json.loads(service_data)
data[ATTR_ENTITY_ID] = entity_ids
if service_domain not in domain_tasks:
domain_tasks[service_domain] = []
domain_tasks[service_domain].append(
hass.services.async_call(service_domain, service, data, blocking)
domain_tasks.append(
hass.services.async_call(service_domain, service, data, blocking,
context)
)
async def async_handle_service_calls(
coro_list: Iterable[Awaitable]) -> None:
"""Handle service calls by domain sequence."""
for coro in coro_list:
await coro
execute_tasks = [async_handle_service_calls(coro_list)
for coro_list in domain_tasks.values()]
if execute_tasks:
await asyncio.wait(execute_tasks, loop=hass.loop)
if domain_tasks:
await asyncio.wait(domain_tasks, loop=hass.loop)
def state_as_number(state: State) -> float:
@ -223,6 +218,9 @@ def state_as_number(state: State) -> float:
Raises ValueError if this is not possible.
"""
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE)
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL):
return 1

View file

@ -0,0 +1,162 @@
"""The tests for reproduction of state."""
import pytest
from homeassistant.components.climate import STATE_HEAT, async_reproduce_states
from homeassistant.components.climate.const import (
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_HOLD_MODE, ATTR_HUMIDITY,
ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE,
SERVICE_SET_HOLD_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON)
from homeassistant.core import Context, State
from tests.common import async_mock_service
ENTITY_1 = 'climate.test1'
ENTITY_2 = 'climate.test2'
@pytest.mark.parametrize(
'service,state', [
(SERVICE_TURN_ON, STATE_ON),
(SERVICE_TURN_OFF, STATE_OFF),
])
async def test_state(hass, service, state):
"""Test that we can turn a state into a service call."""
calls_1 = async_mock_service(hass, DOMAIN, service)
await async_reproduce_states(hass, [
State(ENTITY_1, state)
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
async def test_turn_on_with_mode(hass):
"""Test that state with additional attributes call multiple services."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on',
{ATTR_OPERATION_MODE: STATE_HEAT})
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': ENTITY_1,
ATTR_OPERATION_MODE: STATE_HEAT}
async def test_multiple_same_state(hass):
"""Test that multiple states with same state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'on'),
])
await hass.async_block_till_done()
assert len(calls_1) == 2
# order is not guaranteed
assert any(call.data == {'entity_id': ENTITY_1} for call in calls_1)
assert any(call.data == {'entity_id': ENTITY_2} for call in calls_1)
async def test_multiple_different_state(hass):
"""Test that multiple states with different state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'off'),
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': ENTITY_2}
async def test_state_with_context(hass):
"""Test that context is forwarded."""
calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
context = Context()
await async_reproduce_states(hass, [
State(ENTITY_1, 'on')
], context)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data == {'entity_id': ENTITY_1}
assert calls[0].context == context
async def test_attribute_no_state(hass):
"""Test that no state service call is made with none state."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
value = "dummy"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{ATTR_OPERATION_MODE: value})
])
await hass.async_block_till_done()
assert len(calls_1) == 0
assert len(calls_2) == 0
assert len(calls_3) == 1
assert calls_3[0].data == {'entity_id': ENTITY_1,
ATTR_OPERATION_MODE: value}
@pytest.mark.parametrize(
'service,attribute', [
(SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE),
(SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT),
(SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE),
(SERVICE_SET_HOLD_MODE, ATTR_HOLD_MODE),
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
(SERVICE_SET_HUMIDITY, ATTR_HUMIDITY),
(SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE),
(SERVICE_SET_TEMPERATURE, ATTR_TARGET_TEMP_HIGH),
(SERVICE_SET_TEMPERATURE, ATTR_TARGET_TEMP_LOW),
])
async def test_attribute(hass, service, attribute):
"""Test that service call is made for each attribute."""
calls_1 = async_mock_service(hass, DOMAIN, service)
value = "dummy"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{attribute: value})
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1,
attribute: value}

View file

@ -0,0 +1,45 @@
"""The tests for reproduction of state."""
from asyncio import Future
from unittest.mock import patch
from homeassistant.components.group import async_reproduce_states
from homeassistant.core import Context, State
async def test_reproduce_group(hass):
"""Test reproduce_state with group."""
context = Context()
def clone_state(state, entity_id):
"""Return a cloned state with different entity_id."""
return State(entity_id,
state.state,
state.attributes,
last_changed=state.last_changed,
last_updated=state.last_updated,
context=state.context)
with patch('homeassistant.helpers.state.async_reproduce_state') as fun:
fun.return_value = Future()
fun.return_value.set_result(None)
hass.states.async_set('group.test', 'off', {
'entity_id': ['light.test1', 'light.test2', 'switch.test1']})
hass.states.async_set('light.test1', 'off')
hass.states.async_set('light.test2', 'off')
hass.states.async_set('switch.test1', 'off')
state = State('group.test', 'on')
await async_reproduce_states(
hass,
[state],
context)
fun.assert_called_once_with(
hass,
[clone_state(state, 'light.test1'),
clone_state(state, 'light.test2'),
clone_state(state, 'switch.test1')],
blocking=True,
context=context)

View file

@ -0,0 +1,199 @@
"""The tests for reproduction of state."""
import pytest
from homeassistant.components.media_player import async_reproduce_states
from homeassistant.components.media_player.const import (
ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, DOMAIN, SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE)
from homeassistant.const import (
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK,
SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED,
STATE_PLAYING)
from homeassistant.core import Context, State
from tests.common import async_mock_service
ENTITY_1 = 'media_player.test1'
ENTITY_2 = 'media_player.test2'
@pytest.mark.parametrize(
'service,state', [
(SERVICE_TURN_ON, STATE_ON),
(SERVICE_TURN_OFF, STATE_OFF),
(SERVICE_MEDIA_PLAY, STATE_PLAYING),
(SERVICE_MEDIA_STOP, STATE_IDLE),
(SERVICE_MEDIA_PAUSE, STATE_PAUSED),
])
async def test_state(hass, service, state):
"""Test that we can turn a state into a service call."""
calls_1 = async_mock_service(hass, DOMAIN, service)
await async_reproduce_states(hass, [
State(ENTITY_1, state)
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
async def test_turn_on_with_mode(hass):
"""Test that state with additional attributes call multiple services."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on',
{ATTR_SOUND_MODE: 'dummy'})
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': ENTITY_1,
ATTR_SOUND_MODE: 'dummy'}
async def test_multiple_same_state(hass):
"""Test that multiple states with same state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'on'),
])
await hass.async_block_till_done()
assert len(calls_1) == 2
# order is not guaranteed
assert any(call.data == {'entity_id': 'media_player.test1'}
for call in calls_1)
assert any(call.data == {'entity_id': 'media_player.test2'}
for call in calls_1)
async def test_multiple_different_state(hass):
"""Test that multiple states with different state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'off'),
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': 'media_player.test1'}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': 'media_player.test2'}
async def test_state_with_context(hass):
"""Test that context is forwarded."""
calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
context = Context()
await async_reproduce_states(hass, [
State(ENTITY_1, 'on')
], context)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data == {'entity_id': ENTITY_1}
assert calls[0].context == context
async def test_attribute_no_state(hass):
"""Test that no state service call is made with none state."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE)
value = "dummy"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{ATTR_SOUND_MODE: value})
])
await hass.async_block_till_done()
assert len(calls_1) == 0
assert len(calls_2) == 0
assert len(calls_3) == 1
assert calls_3[0].data == {'entity_id': ENTITY_1,
ATTR_SOUND_MODE: value}
@pytest.mark.parametrize(
'service,attribute', [
(SERVICE_VOLUME_SET, ATTR_MEDIA_VOLUME_LEVEL),
(SERVICE_VOLUME_MUTE, ATTR_MEDIA_VOLUME_MUTED),
(SERVICE_MEDIA_SEEK, ATTR_MEDIA_SEEK_POSITION),
(SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE),
(SERVICE_SELECT_SOUND_MODE, ATTR_SOUND_MODE),
])
async def test_attribute(hass, service, attribute):
"""Test that service call is made for each attribute."""
calls_1 = async_mock_service(hass, DOMAIN, service)
value = "dummy"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{attribute: value})
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1,
attribute: value}
async def test_play_media(hass):
"""Test that no state service call is made with none state."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_PLAY_MEDIA)
value_1 = "dummy_1"
value_2 = "dummy_2"
value_3 = "dummy_3"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{ATTR_MEDIA_CONTENT_TYPE: value_1,
ATTR_MEDIA_CONTENT_ID: value_2})
])
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{ATTR_MEDIA_CONTENT_TYPE: value_1,
ATTR_MEDIA_CONTENT_ID: value_2,
ATTR_MEDIA_ENQUEUE: value_3})
])
await hass.async_block_till_done()
assert len(calls_1) == 2
assert calls_1[0].data == {'entity_id': ENTITY_1,
ATTR_MEDIA_CONTENT_TYPE: value_1,
ATTR_MEDIA_CONTENT_ID: value_2}
assert calls_1[1].data == {'entity_id': ENTITY_1,
ATTR_MEDIA_CONTENT_TYPE: value_1,
ATTR_MEDIA_CONTENT_ID: value_2,
ATTR_MEDIA_ENQUEUE: value_3}

View file

@ -15,8 +15,6 @@ from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED,
STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME)
from homeassistant.components.media_player import (
SERVICE_PLAY_MEDIA, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON)
@ -50,6 +48,40 @@ def test_async_track_states(hass):
sorted(states, key=lambda state: state.entity_id)
@asyncio.coroutine
def test_call_to_component(hass):
"""Test calls to components state reproduction functions."""
with patch(('homeassistant.components.media_player.'
'async_reproduce_states')) as media_player_fun:
media_player_fun.return_value = asyncio.Future()
media_player_fun.return_value.set_result(None)
with patch(('homeassistant.components.climate.'
'async_reproduce_states')) as climate_fun:
climate_fun.return_value = asyncio.Future()
climate_fun.return_value.set_result(None)
state_media_player = ha.State('media_player.test', 'bad')
state_climate = ha.State('climate.test', 'bad')
context = "dummy_context"
yield from state.async_reproduce_state(
hass,
[state_media_player, state_climate],
blocking=True,
context=context)
media_player_fun.assert_called_once_with(
hass,
[state_media_player],
context=context)
climate_fun.assert_called_once_with(
hass,
[state_climate],
context=context)
class TestStateHelpers(unittest.TestCase):
"""Test the Home Assistant event helpers."""
@ -147,63 +179,6 @@ class TestStateHelpers(unittest.TestCase):
assert SERVICE_TURN_ON == last_call.service
assert complex_data == last_call.data.get('complex')
def test_reproduce_media_data(self):
"""Test reproduce_state with SERVICE_PLAY_MEDIA."""
calls = mock_service(self.hass, 'media_player', SERVICE_PLAY_MEDIA)
self.hass.states.set('media_player.test', 'off')
media_attributes = {'media_content_type': 'movie',
'media_content_id': 'batman'}
state.reproduce_state(self.hass, ha.State('media_player.test', 'None',
media_attributes))
self.hass.block_till_done()
assert len(calls) > 0
last_call = calls[-1]
assert 'media_player' == last_call.domain
assert SERVICE_PLAY_MEDIA == last_call.service
assert 'movie' == last_call.data.get('media_content_type')
assert 'batman' == last_call.data.get('media_content_id')
def test_reproduce_media_play(self):
"""Test reproduce_state with SERVICE_MEDIA_PLAY."""
calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PLAY)
self.hass.states.set('media_player.test', 'off')
state.reproduce_state(
self.hass, ha.State('media_player.test', 'playing'))
self.hass.block_till_done()
assert len(calls) > 0
last_call = calls[-1]
assert 'media_player' == last_call.domain
assert SERVICE_MEDIA_PLAY == last_call.service
assert ['media_player.test'] == \
last_call.data.get('entity_id')
def test_reproduce_media_pause(self):
"""Test reproduce_state with SERVICE_MEDIA_PAUSE."""
calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PAUSE)
self.hass.states.set('media_player.test', 'playing')
state.reproduce_state(
self.hass, ha.State('media_player.test', 'paused'))
self.hass.block_till_done()
assert len(calls) > 0
last_call = calls[-1]
assert 'media_player' == last_call.domain
assert SERVICE_MEDIA_PAUSE == last_call.service
assert ['media_player.test'] == \
last_call.data.get('entity_id')
def test_reproduce_bad_state(self):
"""Test reproduce_state with bad state."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
@ -217,45 +192,6 @@ class TestStateHelpers(unittest.TestCase):
assert len(calls) == 0
assert 'off' == self.hass.states.get('light.test').state
def test_reproduce_group(self):
"""Test reproduce_state with group."""
light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('group.test', 'off', {
'entity_id': ['light.test1', 'light.test2']})
state.reproduce_state(self.hass, ha.State('group.test', 'on'))
self.hass.block_till_done()
assert 1 == len(light_calls)
last_call = light_calls[-1]
assert 'light' == last_call.domain
assert SERVICE_TURN_ON == last_call.service
assert ['light.test1', 'light.test2'] == \
last_call.data.get('entity_id')
def test_reproduce_group_same_data(self):
"""Test reproduce_state with group with same domain and data."""
light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test1', 'off')
self.hass.states.set('light.test2', 'off')
state.reproduce_state(self.hass, [
ha.State('light.test1', 'on', {'brightness': 95}),
ha.State('light.test2', 'on', {'brightness': 95})])
self.hass.block_till_done()
assert 1 == len(light_calls)
last_call = light_calls[-1]
assert 'light' == last_call.domain
assert SERVICE_TURN_ON == last_call.service
assert ['light.test1', 'light.test2'] == \
last_call.data.get('entity_id')
assert 95 == last_call.data.get('brightness')
def test_as_number_states(self):
"""Test state_as_number with states."""
zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED,