Merge pull request #1485 from MartinHjelmare/refactor-scene-reproduce_state

Refactor reproduce_state for scene component
This commit is contained in:
Paulus Schoutsen 2016-03-09 15:29:10 -08:00
commit 47c4f66886
10 changed files with 247 additions and 97 deletions

View File

@ -1,5 +1,5 @@
""" """
Component to interface with a alarm control panel. Component to interface with an alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/ https://home-assistant.io/components/alarm_control_panel/
@ -9,7 +9,7 @@ import os
from homeassistant.components import verisure from homeassistant.components import verisure
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -32,9 +32,6 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_TRIGGER: 'alarm_trigger' SERVICE_ALARM_TRIGGER: 'alarm_trigger'
} }
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
ATTR_TO_PROPERTY = [ ATTR_TO_PROPERTY = [
ATTR_CODE, ATTR_CODE,
ATTR_CODE_FORMAT ATTR_CODE_FORMAT

View File

@ -13,8 +13,8 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import ( from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
ATTR_ENTITY_ID) STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
from homeassistant.components import (group, verisure, wink) from homeassistant.components import (group, verisure, wink)
DOMAIN = 'lock' DOMAIN = 'lock'
@ -25,10 +25,6 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_LOCKED = "locked"
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms # Maps discovered services to their platforms

View File

@ -8,10 +8,10 @@ import logging
from homeassistant.components.verisure import HUB as hub from homeassistant.components.verisure import HUB as hub
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED from homeassistant.const import (
ATTR_CODE, STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_CODE = 'code'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -108,9 +108,10 @@ ATTR_TO_PROPERTY = [
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
"""Return true if specified media player entity_id is on. """
Return true if specified media player entity_id is on.
Will check all media player if no entity_id specified. Check all media player if no entity_id specified.
""" """
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_OFF) return any(not hass.states.is_state(entity_id, STATE_OFF)
@ -118,19 +119,19 @@ def is_on(hass, entity_id=None):
def turn_on(hass, entity_id=None): def turn_on(hass, entity_id=None):
"""Will turn on specified media player or all.""" """Turn on specified media player or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_ON, data) hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None): def turn_off(hass, entity_id=None):
"""Will turn off specified media player or all.""" """Turn off specified media player or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def toggle(hass, entity_id=None): def toggle(hass, entity_id=None):
"""Will toggle specified media player or all.""" """Toggle specified media player or all."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TOGGLE, data) hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
@ -148,7 +149,7 @@ def volume_down(hass, entity_id=None):
def mute_volume(hass, mute, entity_id=None): def mute_volume(hass, mute, entity_id=None):
"""Send the media player the command for volume down.""" """Send the media player the command for muting the volume."""
data = {ATTR_MEDIA_VOLUME_MUTED: mute} data = {ATTR_MEDIA_VOLUME_MUTED: mute}
if entity_id: if entity_id:
@ -158,7 +159,7 @@ def mute_volume(hass, mute, entity_id=None):
def set_volume_level(hass, volume, entity_id=None): def set_volume_level(hass, volume, entity_id=None):
"""Send the media player the command for volume down.""" """Send the media player the command for setting the volume."""
data = {ATTR_MEDIA_VOLUME_LEVEL: volume} data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
if entity_id: if entity_id:
@ -180,7 +181,7 @@ def media_play(hass, entity_id=None):
def media_pause(hass, entity_id=None): def media_pause(hass, entity_id=None):
"""Send the media player the command for play/pause.""" """Send the media player the command for pause."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data) hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
@ -206,7 +207,8 @@ def media_seek(hass, position, entity_id=None):
def play_media(hass, media_type, media_id, entity_id=None): def play_media(hass, media_type, media_id, entity_id=None):
"""Send the media player the command for playing media.""" """Send the media player the command for playing media."""
data = {"media_type": media_type, "media_id": media_id} data = {ATTR_MEDIA_CONTENT_TYPE: media_type,
ATTR_MEDIA_CONTENT_ID: media_id}
if entity_id: if entity_id:
data[ATTR_ENTITY_ID] = entity_id data[ATTR_ENTITY_ID] = entity_id
@ -297,8 +299,8 @@ def setup(hass, config):
def play_media_service(service): def play_media_service(service):
"""Play specified media_id on the media player.""" """Play specified media_id on the media player."""
media_type = service.data.get('media_type') media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
media_id = service.data.get('media_id') media_id = service.data.get(ATTR_MEDIA_CONTENT_ID)
if media_type is None: if media_type is None:
return return
@ -320,10 +322,12 @@ def setup(hass, config):
class MediaPlayerDevice(Entity): class MediaPlayerDevice(Entity):
"""An abstract class for media player devices.""" """ABC for media player devices."""
# pylint: disable=too-many-public-methods,no-self-use # pylint: disable=too-many-public-methods,no-self-use
# Implement these for your media player # Implement these for your media player
@property @property
def state(self): def state(self):
"""State of the player.""" """State of the player."""
@ -366,37 +370,37 @@ class MediaPlayerDevice(Entity):
@property @property
def media_artist(self): def media_artist(self):
"""Artist of current playing media (Music track only).""" """Artist of current playing media, music track only."""
return None return None
@property @property
def media_album_name(self): def media_album_name(self):
"""Album name of current playing media (Music track only).""" """Album name of current playing media, music track only."""
return None return None
@property @property
def media_album_artist(self): def media_album_artist(self):
"""Album artist of current playing media (Music track only).""" """Album artist of current playing media, music track only."""
return None return None
@property @property
def media_track(self): def media_track(self):
"""Track number of current playing media (Music track only).""" """Track number of current playing media, music track only."""
return None return None
@property @property
def media_series_title(self): def media_series_title(self):
"""The title of the series of current playing media (TV Show only).""" """Title of series of current playing media, TV show only."""
return None return None
@property @property
def media_season(self): def media_season(self):
"""Season of current playing media (TV Show only).""" """Season of current playing media, TV show only."""
return None return None
@property @property
def media_episode(self): def media_episode(self):
"""Episode of current playing media (TV Show only).""" """Episode of current playing media, TV show only."""
return None return None
@property @property
@ -421,7 +425,7 @@ class MediaPlayerDevice(Entity):
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag of media commands that are supported.""" """Flag media commands that are supported."""
return 0 return 0
def turn_on(self): def turn_on(self):
@ -508,17 +512,17 @@ class MediaPlayerDevice(Entity):
self.turn_off() self.turn_off()
def volume_up(self): def volume_up(self):
"""volume_up media player.""" """Turn volume up for media player."""
if self.volume_level < 1: if self.volume_level < 1:
self.set_volume_level(min(1, self.volume_level + .1)) self.set_volume_level(min(1, self.volume_level + .1))
def volume_down(self): def volume_down(self):
"""volume_down media player.""" """Turn volume down for media player."""
if self.volume_level > 0: if self.volume_level > 0:
self.set_volume_level(max(0, self.volume_level - .1)) self.set_volume_level(max(0, self.volume_level - .1))
def media_play_pause(self): def media_play_pause(self):
"""media_play_pause media player.""" """Play or pause the media player."""
if self.state == STATE_PLAYING: if self.state == STATE_PLAYING:
self.media_pause() self.media_pause()
else: else:
@ -526,7 +530,7 @@ class MediaPlayerDevice(Entity):
@property @property
def entity_picture(self): def entity_picture(self):
"""Return the image of the media playing.""" """Return image of the media playing."""
return None if self.state == STATE_OFF else self.media_image_url return None if self.state == STATE_OFF else self.media_image_url
@property @property

View File

@ -43,7 +43,6 @@ REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the universal media players.""" """Setup the universal media players."""
if not validate_config(config): if not validate_config(config):
@ -200,7 +199,7 @@ class UniversalMediaPlayer(MediaPlayerDevice):
@property @property
def master_state(self): def master_state(self):
"""Get the master state from entity or none.""" """Return the master state for entity or None."""
if CONF_STATE in self._attrs: if CONF_STATE in self._attrs:
master_state = self._entity_lkp(self._attrs[CONF_STATE][0], master_state = self._entity_lkp(self._attrs[CONF_STATE][0],
self._attrs[CONF_STATE][1]) self._attrs[CONF_STATE][1])
@ -324,7 +323,7 @@ class UniversalMediaPlayer(MediaPlayerDevice):
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag of media commands that are supported.""" """Flag media commands that are supported."""
flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0 flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0
if SERVICE_TURN_ON in self._cmds: if SERVICE_TURN_ON in self._cmds:
@ -345,7 +344,7 @@ class UniversalMediaPlayer(MediaPlayerDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Extra attributes a device wants to expose.""" """Return device specific state attributes."""
active_child = self._child_state active_child = self._child_state
return {ATTR_ACTIVE_CHILD: active_child.entity_id} \ return {ATTR_ACTIVE_CHILD: active_child.entity_id} \
if active_child else {} if active_child else {}
@ -391,23 +390,24 @@ class UniversalMediaPlayer(MediaPlayerDevice):
def play_media(self, media_type, media_id): def play_media(self, media_type, media_id):
"""Play a piece of media.""" """Play a piece of media."""
data = {'media_type': media_type, 'media_id': media_id} data = {ATTR_MEDIA_CONTENT_TYPE: media_type,
ATTR_MEDIA_CONTENT_ID: media_id}
self._call_service(SERVICE_PLAY_MEDIA, data) self._call_service(SERVICE_PLAY_MEDIA, data)
def volume_up(self): def volume_up(self):
"""Volume up media player.""" """Turn volume up for media player."""
self._call_service(SERVICE_VOLUME_UP, allow_override=True) self._call_service(SERVICE_VOLUME_UP, allow_override=True)
def volume_down(self): def volume_down(self):
"""Volume down media player.""" """Turn volume down for media player."""
self._call_service(SERVICE_VOLUME_DOWN, allow_override=True) self._call_service(SERVICE_VOLUME_DOWN, allow_override=True)
def media_play_pause(self): def media_play_pause(self):
"""Send play/pause command media player.""" """Play or pause the media player."""
self._call_service(SERVICE_MEDIA_PLAY_PAUSE) self._call_service(SERVICE_MEDIA_PLAY_PAUSE)
def update(self): def update(self):
"""Event to trigger a state update.""" """Update state in HA."""
for child_name in self._children: for child_name in self._children:
child_state = self.hass.states.get(child_name) child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES: if child_state and child_state.state not in OFF_STATES:

View File

@ -33,7 +33,7 @@ def activate(hass, entity_id=None):
def setup(hass, config): def setup(hass, config):
"""Setup the scenes.""" """Setup scenes."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# You are not allowed to mutate the original config so make a copy # You are not allowed to mutate the original config so make a copy
@ -76,9 +76,9 @@ class Scene(Entity):
@property @property
def state(self): def state(self):
"""Return the state.""" """Return the state of the scene."""
return STATE return STATE
def activate(self): def activate(self):
"""Activate scene. Tries to get entities into requested state.""" """Activate scene. Try to get entities into requested state."""
raise NotImplementedError raise NotImplementedError

View File

@ -1,5 +1,5 @@
""" """
Allows users to set and activate scenes. Allow users to set and activate scenes.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scene/ https://home-assistant.io/components/scene/
@ -20,7 +20,6 @@ CONF_ENTITIES = "entities"
SceneConfig = namedtuple('SceneConfig', ['name', 'states']) SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup home assistant scene entries.""" """Setup home assistant scene entries."""
scene_config = config.get("states") scene_config = config.get("states")
@ -83,5 +82,5 @@ class HomeAssistantScene(Scene):
} }
def activate(self): def activate(self):
"""Activate scene. Tries to get entities into requested state.""" """Activate scene. Try to get entities into requested state."""
reproduce_state(self.hass, self.scene_config.states.values(), True) reproduce_state(self.hass, self.scene_config.states.values(), True)

View File

@ -102,6 +102,10 @@ ATTR_LOCATION = "location"
ATTR_BATTERY_LEVEL = "battery_level" ATTR_BATTERY_LEVEL = "battery_level"
# For devices which support a code attribute
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
# For devices which support an armed state # For devices which support an armed state
ATTR_ARMED = "device_armed" ATTR_ARMED = "device_armed"

View File

@ -4,24 +4,73 @@ import logging
from collections import defaultdict from collections import defaultdict
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.media_player import SERVICE_PLAY_MEDIA 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)
from homeassistant.components.notify import (
ATTR_MESSAGE, SERVICE_NOTIFY)
from homeassistant.components.sun import ( from homeassistant.components.sun import (
STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON)
from homeassistant.components.thermostat import (
ATTR_AWAY_MODE, ATTR_FAN, SERVICE_SET_AWAY_MODE, SERVICE_SET_FAN_MODE,
SERVICE_SET_TEMPERATURE)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_TURN_OFF, ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_ALARM_ARM_AWAY,
SERVICE_TURN_ON, STATE_CLOSED, STATE_LOCKED, STATE_OFF, STATE_ON, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER,
STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_UNLOCKED) SERVICE_CLOSE, SERVICE_LOCK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_SEEK, SERVICE_MOVE_DOWN, SERVICE_MOVE_UP, SERVICE_OPEN,
SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_CLOSED, STATE_LOCKED,
STATE_OFF, STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING,
STATE_UNKNOWN, STATE_UNLOCKED)
from homeassistant.core import State from homeassistant.core import State
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
GROUP_DOMAIN = 'group'
HASS_DOMAIN = 'homeassistant'
# 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],
SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE],
}
# Update this dict when new services are added to HA.
# Each item is a service with a corresponding state.
SERVICE_TO_STATE = {
SERVICE_TURN_ON: STATE_ON,
SERVICE_TURN_OFF: STATE_OFF,
SERVICE_MEDIA_PLAY: STATE_PLAYING,
SERVICE_MEDIA_PAUSE: STATE_PAUSED,
SERVICE_ALARM_ARM_AWAY: STATE_ALARM_ARMED_AWAY,
SERVICE_ALARM_ARM_HOME: STATE_ALARM_ARMED_HOME,
SERVICE_ALARM_DISARM: STATE_ALARM_DISARMED,
SERVICE_ALARM_TRIGGER: STATE_ALARM_TRIGGERED,
SERVICE_LOCK: STATE_LOCKED,
SERVICE_UNLOCK: STATE_UNLOCKED,
SERVICE_CLOSE: STATE_CLOSED,
SERVICE_OPEN: STATE_OPEN,
SERVICE_MOVE_UP: STATE_OPEN,
SERVICE_MOVE_DOWN: STATE_CLOSED,
}
# pylint: disable=too-few-public-methods, attribute-defined-outside-init # pylint: disable=too-few-public-methods, attribute-defined-outside-init
class TrackStates(object): class TrackStates(object):
"""Record the time when the with-block is entered. """
Record the time when the with-block is entered.
Will add all states that have changed since the start time to the return Add all states that have changed since the start time to the return list
list when with-block is exited. when with-block is exited.
""" """
def __init__(self, hass): def __init__(self, hass):
@ -40,7 +89,7 @@ class TrackStates(object):
def get_changed_since(states, utc_point_in_time): def get_changed_since(states, utc_point_in_time):
"""List of states that have been changed since utc_point_in_time.""" """Return list of states that have been changed since utc_point_in_time."""
point_in_time = dt_util.strip_microseconds(utc_point_in_time) point_in_time = dt_util.strip_microseconds(utc_point_in_time)
return [state for state in states if state.last_updated >= point_in_time] return [state for state in states if state.last_updated >= point_in_time]
@ -54,35 +103,36 @@ def reproduce_state(hass, states, blocking=False):
to_call = defaultdict(list) to_call = defaultdict(list)
for state in states: for state in states:
current_state = hass.states.get(state.entity_id)
if current_state is None: if hass.states.get(state.entity_id) is None:
_LOGGER.warning('reproduce_state: Unable to find entity %s', _LOGGER.warning('reproduce_state: Unable to find entity %s',
state.entity_id) state.entity_id)
continue continue
if state.domain == 'media_player' and state.attributes and \ if state.domain == GROUP_DOMAIN:
'media_type' in state.attributes and \ service_domain = HASS_DOMAIN
'media_id' in state.attributes:
service = SERVICE_PLAY_MEDIA
elif state.domain == 'media_player' and state.state == STATE_PAUSED:
service = SERVICE_MEDIA_PAUSE
elif state.domain == 'media_player' and state.state == STATE_PLAYING:
service = SERVICE_MEDIA_PLAY
elif state.state == STATE_ON:
service = SERVICE_TURN_ON
elif state.state == STATE_OFF:
service = SERVICE_TURN_OFF
else: else:
service_domain = state.domain
domain_services = hass.services.services[service_domain]
service = None
for _service in domain_services.keys():
if (_service in SERVICE_ATTRIBUTES and
all(attr in state.attributes
for attr in SERVICE_ATTRIBUTES[_service]) or
_service in SERVICE_TO_STATE and
SERVICE_TO_STATE[_service] == state.state):
service = _service
if (_service in SERVICE_TO_STATE and
SERVICE_TO_STATE[_service] == state.state):
break
if not service:
_LOGGER.warning("reproduce_state: Unable to reproduce state %s", _LOGGER.warning("reproduce_state: Unable to reproduce state %s",
state) state)
continue continue
if state.domain == 'group':
service_domain = 'homeassistant'
else:
service_domain = state.domain
# We group service calls for entities by service call # We group service calls for entities by service call
# json used to create a hashable version of dict with maybe lists in it # json used to create a hashable version of dict with maybe lists in it
key = (service_domain, service, key = (service_domain, service,
@ -96,7 +146,8 @@ def reproduce_state(hass, states, blocking=False):
def state_as_number(state): def state_as_number(state):
"""Try to coerce our state to a number. """
Try to coerce our state to a number.
Raises ValueError if this is not possible. Raises ValueError if this is not possible.
""" """

View File

@ -5,13 +5,15 @@ from unittest.mock import patch
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.components as core_components import homeassistant.components as core_components
from homeassistant.const import SERVICE_TURN_ON from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.helpers import state from homeassistant.helpers import state
from homeassistant.const import ( from homeassistant.const import (
STATE_OPEN, STATE_CLOSED, STATE_OPEN, STATE_CLOSED,
STATE_LOCKED, STATE_UNLOCKED, STATE_LOCKED, STATE_UNLOCKED,
STATE_ON, STATE_OFF) STATE_ON, STATE_OFF)
from homeassistant.components.media_player import (
SERVICE_PLAY_MEDIA, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON, from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON) STATE_BELOW_HORIZON)
@ -22,16 +24,16 @@ class TestStateHelpers(unittest.TestCase):
"""Test the Home Assistant event helpers.""" """Test the Home Assistant event helpers."""
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started.""" """Run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
core_components.setup(self.hass, {}) core_components.setup(self.hass, {})
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Stop down everything that was started.""" """Stop when tests are finished."""
self.hass.stop() self.hass.stop()
def test_get_changed_since(self): def test_get_changed_since(self):
"""Test for changes since.""" """Test get_changed_since."""
point1 = dt_util.utcnow() point1 = dt_util.utcnow()
point2 = point1 + timedelta(seconds=5) point2 = point1 + timedelta(seconds=5)
point3 = point2 + timedelta(seconds=5) point3 = point2 + timedelta(seconds=5)
@ -77,8 +79,19 @@ class TestStateHelpers(unittest.TestCase):
sorted([state2, state3], key=lambda state: state.entity_id), sorted([state2, state3], key=lambda state: state.entity_id),
sorted(states, key=lambda state: state.entity_id)) sorted(states, key=lambda state: state.entity_id))
def test_reproduce_state_with_turn_on(self): def test_reproduce_with_no_entity(self):
"""Test reproduction of state with turn_on.""" """Test reproduce_state with no entity."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
state.reproduce_state(self.hass, ha.State('light.test', 'on'))
self.hass.pool.block_till_done()
self.assertTrue(len(calls) == 0)
self.assertEqual(None, self.hass.states.get('light.test'))
def test_reproduce_turn_on(self):
"""Test reproduce_state with SERVICE_TURN_ON."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test', 'off') self.hass.states.set('light.test', 'off')
@ -93,8 +106,24 @@ class TestStateHelpers(unittest.TestCase):
self.assertEqual(SERVICE_TURN_ON, last_call.service) self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(['light.test'], last_call.data.get('entity_id')) self.assertEqual(['light.test'], last_call.data.get('entity_id'))
def test_reproduce_state_with_complex_service_data(self): def test_reproduce_turn_off(self):
"""Test reproduction of state with complex service data.""" """Test reproduce_state with SERVICE_TURN_OFF."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF)
self.hass.states.set('light.test', 'on')
state.reproduce_state(self.hass, ha.State('light.test', 'off'))
self.hass.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('light', last_call.domain)
self.assertEqual(SERVICE_TURN_OFF, last_call.service)
self.assertEqual(['light.test'], last_call.data.get('entity_id'))
def test_reproduce_complex_data(self):
"""Test reproduce_state with complex service data."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test', 'off') self.hass.states.set('light.test', 'off')
@ -113,8 +142,78 @@ class TestStateHelpers(unittest.TestCase):
self.assertEqual(SERVICE_TURN_ON, last_call.service) self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(complex_data, last_call.data.get('complex')) self.assertEqual(complex_data, last_call.data.get('complex'))
def test_reproduce_state_with_group(self): def test_reproduce_media_data(self):
"""Test reproduction of state with group.""" """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.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('media_player', last_call.domain)
self.assertEqual(SERVICE_PLAY_MEDIA, last_call.service)
self.assertEqual('movie', last_call.data.get('media_content_type'))
self.assertEqual('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.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('media_player', last_call.domain)
self.assertEqual(SERVICE_MEDIA_PLAY, last_call.service)
self.assertEqual(['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.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('media_player', last_call.domain)
self.assertEqual(SERVICE_MEDIA_PAUSE, last_call.service)
self.assertEqual(['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)
self.hass.states.set('light.test', 'off')
state.reproduce_state(self.hass, ha.State('light.test', 'bad'))
self.hass.pool.block_till_done()
self.assertTrue(len(calls) == 0)
self.assertEqual('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) light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('group.test', 'off', { self.hass.states.set('group.test', 'off', {
@ -131,8 +230,8 @@ class TestStateHelpers(unittest.TestCase):
self.assertEqual(['light.test1', 'light.test2'], self.assertEqual(['light.test1', 'light.test2'],
last_call.data.get('entity_id')) last_call.data.get('entity_id'))
def test_reproduce_state_group_states_with_same_domain_and_data(self): def test_reproduce_group_same_data(self):
"""Test reproduction of state with the dame domain.""" """Test reproduce_state with group with same domain and data."""
light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test1', 'off') self.hass.states.set('light.test1', 'off')
@ -153,7 +252,7 @@ class TestStateHelpers(unittest.TestCase):
self.assertEqual(95, last_call.data.get('brightness')) self.assertEqual(95, last_call.data.get('brightness'))
def test_as_number_states(self): def test_as_number_states(self):
"""Test number as states.""" """Test state_as_number with states."""
zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED, zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED,
STATE_BELOW_HORIZON) STATE_BELOW_HORIZON)
one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON) one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON)
@ -165,7 +264,7 @@ class TestStateHelpers(unittest.TestCase):
ha.State('domain.test', _state, {}))) ha.State('domain.test', _state, {})))
def test_as_number_coercion(self): def test_as_number_coercion(self):
"""Test numbers.""" """Test state_as_number with number."""
for _state in ('0', '0.0', 0, 0.0): for _state in ('0', '0.0', 0, 0.0):
self.assertEqual( self.assertEqual(
0.0, state.state_as_number( 0.0, state.state_as_number(
@ -176,7 +275,7 @@ class TestStateHelpers(unittest.TestCase):
ha.State('domain.test', _state, {}))) ha.State('domain.test', _state, {})))
def test_as_number_invalid_cases(self): def test_as_number_invalid_cases(self):
""".""" """Test state_as_number with invalid cases."""
for _state in ('', 'foo', 'foo.bar', None, False, True, object, for _state in ('', 'foo', 'foo.bar', None, False, True, object,
object()): object()):
self.assertRaises(ValueError, self.assertRaises(ValueError,