mirror of
https://github.com/home-assistant/core.git
synced 2025-04-28 19:27:51 +00:00
Merge pull request #1485 from MartinHjelmare/refactor-scene-reproduce_state
Refactor reproduce_state for scene component
This commit is contained in:
commit
47c4f66886
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user