diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 362c4ee93e4..e1d3093995c 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -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, diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py new file mode 100644 index 00000000000..2f84ee27bbd --- /dev/null +++ b/homeassistant/components/climate/const.py @@ -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' diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py new file mode 100644 index 00000000000..3259e4084cf --- /dev/null +++ b/homeassistant/components/climate/reproduce_state.py @@ -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]) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index b6dcd65fc2c..d1cd88a8438 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -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 + '.{}' diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py new file mode 100644 index 00000000000..1cf1793e6f6 --- /dev/null +++ b/homeassistant/components/group/reproduce_state.py @@ -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) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 6f74380728c..840b745eebd 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -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' diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py new file mode 100644 index 00000000000..b926d893414 --- /dev/null +++ b/homeassistant/components/media_player/const.py @@ -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' diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py new file mode 100644 index 00000000000..cbe98704615 --- /dev/null +++ b/homeassistant/components/media_player/reproduce_state.py @@ -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]) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 8fd3f7d053e..7d69defed48 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -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, - states: Union[State, Iterable[State]], - blocking: bool = False) -> None: - """Reproduce given state.""" +async def async_reproduce_state( + hass: HomeAssistantType, + states: Union[State, Iterable[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 diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py new file mode 100644 index 00000000000..c16151320b4 --- /dev/null +++ b/tests/components/climate/test_reproduce_state.py @@ -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} diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py new file mode 100644 index 00000000000..43bc2a03fe9 --- /dev/null +++ b/tests/components/group/test_reproduce_state.py @@ -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) diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py new file mode 100644 index 00000000000..f39733178b1 --- /dev/null +++ b/tests/components/media_player/test_reproduce_state.py @@ -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} diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 07654744492..5c04f085c86 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -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,