mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
5369d8c61c
@ -3,11 +3,6 @@ language: python
|
|||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.4"
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements_all.txt
|
- script/bootstrap_server
|
||||||
- pip install flake8 pylint coveralls
|
|
||||||
script:
|
script:
|
||||||
- flake8 homeassistant
|
- script/cibuild
|
||||||
- pylint homeassistant
|
|
||||||
- coverage run -m unittest discover tests
|
|
||||||
after_success:
|
|
||||||
- coveralls
|
|
||||||
|
@ -103,6 +103,10 @@ def get_arguments():
|
|||||||
'--uninstall-osx',
|
'--uninstall-osx',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Uninstalls from OS X.')
|
help='Uninstalls from OS X.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--restart-osx',
|
||||||
|
action='store_true',
|
||||||
|
help='Restarts on OS X.')
|
||||||
if os.name != "nt":
|
if os.name != "nt":
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--daemon',
|
'--daemon',
|
||||||
@ -216,6 +220,10 @@ def main():
|
|||||||
if args.uninstall_osx:
|
if args.uninstall_osx:
|
||||||
uninstall_osx()
|
uninstall_osx()
|
||||||
return
|
return
|
||||||
|
if args.restart_osx:
|
||||||
|
uninstall_osx()
|
||||||
|
install_osx()
|
||||||
|
return
|
||||||
|
|
||||||
# daemon functions
|
# daemon functions
|
||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
|
108
homeassistant/components/alarm_control_panel/__init__.py
Normal file
108
homeassistant/components/alarm_control_panel/__init__.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.alarm_control_panel
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Component to interface with a alarm control panel.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
from homeassistant.components import verisure
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
|
||||||
|
|
||||||
|
DOMAIN = 'alarm_control_panel'
|
||||||
|
DEPENDENCIES = []
|
||||||
|
SCAN_INTERVAL = 30
|
||||||
|
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
|
# Maps discovered services to their platforms
|
||||||
|
DISCOVERY_PLATFORMS = {
|
||||||
|
verisure.DISCOVER_SENSORS: 'verisure'
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVICE_TO_METHOD = {
|
||||||
|
SERVICE_ALARM_DISARM: 'alarm_disarm',
|
||||||
|
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
|
||||||
|
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
|
||||||
|
}
|
||||||
|
|
||||||
|
ATTR_CODE = 'code'
|
||||||
|
|
||||||
|
ATTR_TO_PROPERTY = [
|
||||||
|
ATTR_CODE,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Track states and offer events for sensors. """
|
||||||
|
component = EntityComponent(
|
||||||
|
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
|
||||||
|
DISCOVERY_PLATFORMS)
|
||||||
|
|
||||||
|
component.setup(config)
|
||||||
|
|
||||||
|
def alarm_service_handler(service):
|
||||||
|
""" Maps services to methods on Alarm. """
|
||||||
|
target_alarms = component.extract_from_service(service)
|
||||||
|
|
||||||
|
if ATTR_CODE not in service.data:
|
||||||
|
return
|
||||||
|
|
||||||
|
code = service.data[ATTR_CODE]
|
||||||
|
|
||||||
|
method = SERVICE_TO_METHOD[service.service]
|
||||||
|
|
||||||
|
for alarm in target_alarms:
|
||||||
|
getattr(alarm, method)(code)
|
||||||
|
|
||||||
|
for service in SERVICE_TO_METHOD:
|
||||||
|
hass.services.register(DOMAIN, service, alarm_service_handler)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def alarm_disarm(hass, code, entity_id=None):
|
||||||
|
""" Send the alarm the command for disarm. """
|
||||||
|
data = {ATTR_CODE: code}
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
|
||||||
|
|
||||||
|
|
||||||
|
def alarm_arm_home(hass, code, entity_id=None):
|
||||||
|
""" Send the alarm the command for arm home. """
|
||||||
|
data = {ATTR_CODE: code}
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
|
||||||
|
|
||||||
|
|
||||||
|
def alarm_arm_away(hass, code, entity_id=None):
|
||||||
|
""" Send the alarm the command for arm away. """
|
||||||
|
data = {ATTR_CODE: code}
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||||
|
|
||||||
|
|
||||||
|
class AlarmControlPanel(Entity):
|
||||||
|
""" ABC for alarm control devices. """
|
||||||
|
def alarm_disarm(self, code):
|
||||||
|
""" Send disarm command. """
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def alarm_arm_home(self, code):
|
||||||
|
""" Send arm home command. """
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def alarm_arm_away(self, code):
|
||||||
|
""" Send arm away command. """
|
||||||
|
raise NotImplementedError()
|
88
homeassistant/components/alarm_control_panel/verisure.py
Normal file
88
homeassistant/components/alarm_control_panel/verisure.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.alarm_control_panel.verisure
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Interfaces with Verisure alarm control panel.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.components.verisure as verisure
|
||||||
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Verisure platform. """
|
||||||
|
|
||||||
|
if not verisure.MY_PAGES:
|
||||||
|
_LOGGER.error('A connection has not been made to Verisure mypages.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
alarms = []
|
||||||
|
|
||||||
|
alarms.extend([
|
||||||
|
VerisureAlarm(value)
|
||||||
|
for value in verisure.get_alarm_status().values()
|
||||||
|
if verisure.SHOW_ALARM
|
||||||
|
])
|
||||||
|
|
||||||
|
add_devices(alarms)
|
||||||
|
|
||||||
|
|
||||||
|
class VerisureAlarm(alarm.AlarmControlPanel):
|
||||||
|
""" represents a Verisure alarm status within home assistant. """
|
||||||
|
|
||||||
|
def __init__(self, alarm_status):
|
||||||
|
self._id = alarm_status.id
|
||||||
|
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
||||||
|
self._state = STATE_UNKNOWN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return 'Alarm {}'.format(self._id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
''' update alarm status '''
|
||||||
|
verisure.update()
|
||||||
|
|
||||||
|
if verisure.STATUS[self._device][self._id].status == 'unarmed':
|
||||||
|
self._state = STATE_ALARM_DISARMED
|
||||||
|
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
|
||||||
|
self._state = STATE_ALARM_ARMED_HOME
|
||||||
|
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
|
||||||
|
self._state = STATE_ALARM_ARMED_AWAY
|
||||||
|
elif verisure.STATUS[self._device][self._id].status != 'pending':
|
||||||
|
_LOGGER.error(
|
||||||
|
'Unknown alarm state %s',
|
||||||
|
verisure.STATUS[self._device][self._id].status)
|
||||||
|
|
||||||
|
def alarm_disarm(self, code):
|
||||||
|
""" Send disarm command. """
|
||||||
|
verisure.MY_PAGES.set_alarm_status(
|
||||||
|
code,
|
||||||
|
verisure.MY_PAGES.ALARM_DISARMED)
|
||||||
|
_LOGGER.warning('disarming')
|
||||||
|
|
||||||
|
def alarm_arm_home(self, code):
|
||||||
|
""" Send arm home command. """
|
||||||
|
verisure.MY_PAGES.set_alarm_status(
|
||||||
|
code,
|
||||||
|
verisure.MY_PAGES.ALARM_ARMED_HOME)
|
||||||
|
_LOGGER.warning('arming home')
|
||||||
|
|
||||||
|
def alarm_arm_away(self, code):
|
||||||
|
""" Send arm away command. """
|
||||||
|
verisure.MY_PAGES.set_alarm_status(
|
||||||
|
code,
|
||||||
|
verisure.MY_PAGES.ALARM_ARMED_AWAY)
|
||||||
|
_LOGGER.warning('arming away')
|
@ -7,63 +7,67 @@ Allows to setup simple automation rules via the config file.
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.bootstrap import prepare_setup_platform
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.helpers import config_per_platform
|
|
||||||
from homeassistant.util import split_entity_id
|
from homeassistant.util import split_entity_id
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
||||||
from homeassistant.components import logbook
|
from homeassistant.components import logbook
|
||||||
|
|
||||||
DOMAIN = "automation"
|
DOMAIN = 'automation'
|
||||||
|
|
||||||
DEPENDENCIES = ["group"]
|
DEPENDENCIES = ['group']
|
||||||
|
|
||||||
CONF_ALIAS = "alias"
|
CONF_ALIAS = 'alias'
|
||||||
CONF_SERVICE = "execute_service"
|
CONF_SERVICE = 'execute_service'
|
||||||
CONF_SERVICE_ENTITY_ID = "service_entity_id"
|
CONF_SERVICE_ENTITY_ID = 'service_entity_id'
|
||||||
CONF_SERVICE_DATA = "service_data"
|
CONF_SERVICE_DATA = 'service_data'
|
||||||
CONF_IF = "if"
|
|
||||||
|
CONF_CONDITION = 'condition'
|
||||||
|
CONF_ACTION = 'action'
|
||||||
|
CONF_TRIGGER = 'trigger'
|
||||||
|
CONF_CONDITION_TYPE = 'condition_type'
|
||||||
|
|
||||||
|
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
|
||||||
|
CONDITION_TYPE_AND = 'and'
|
||||||
|
CONDITION_TYPE_OR = 'or'
|
||||||
|
|
||||||
|
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up automation. """
|
""" Sets up automation. """
|
||||||
success = False
|
config_key = DOMAIN
|
||||||
|
found = 1
|
||||||
|
|
||||||
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
while config_key in config:
|
||||||
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
|
p_config = _migrate_old_config(config[config_key])
|
||||||
|
found += 1
|
||||||
|
config_key = "{} {}".format(DOMAIN, found)
|
||||||
|
|
||||||
if platform is None:
|
name = p_config.get(CONF_ALIAS, config_key)
|
||||||
_LOGGER.error("Unknown automation platform specified: %s", p_type)
|
action = _get_action(hass, p_config.get(CONF_ACTION, {}), name)
|
||||||
continue
|
|
||||||
|
|
||||||
action = _get_action(hass, p_config)
|
|
||||||
|
|
||||||
if action is None:
|
if action is None:
|
||||||
return
|
continue
|
||||||
|
|
||||||
if CONF_IF in p_config:
|
if CONF_CONDITION in p_config or CONF_CONDITION_TYPE in p_config:
|
||||||
action = _process_if(hass, config, p_config[CONF_IF], action)
|
action = _process_if(hass, config, p_config, action)
|
||||||
|
|
||||||
if platform.trigger(hass, p_config, action):
|
if action is None:
|
||||||
_LOGGER.info(
|
continue
|
||||||
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
|
|
||||||
success = True
|
|
||||||
else:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
|
|
||||||
|
|
||||||
return success
|
_process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name,
|
||||||
|
action)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _get_action(hass, config):
|
def _get_action(hass, config, name):
|
||||||
""" Return an action based on a config. """
|
""" Return an action based on a config. """
|
||||||
|
|
||||||
name = config.get(CONF_ALIAS, 'Unnamed automation')
|
|
||||||
|
|
||||||
if CONF_SERVICE not in config:
|
if CONF_SERVICE not in config:
|
||||||
_LOGGER.error('Error setting up %s, no action specified.',
|
_LOGGER.error('Error setting up %s, no action specified.', name)
|
||||||
name)
|
return None
|
||||||
return
|
|
||||||
|
|
||||||
def action():
|
def action():
|
||||||
""" Action to be executed. """
|
""" Action to be executed. """
|
||||||
@ -71,7 +75,6 @@ def _get_action(hass, config):
|
|||||||
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
|
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
|
||||||
|
|
||||||
domain, service = split_entity_id(config[CONF_SERVICE])
|
domain, service = split_entity_id(config[CONF_SERVICE])
|
||||||
|
|
||||||
service_data = config.get(CONF_SERVICE_DATA, {})
|
service_data = config.get(CONF_SERVICE_DATA, {})
|
||||||
|
|
||||||
if not isinstance(service_data, dict):
|
if not isinstance(service_data, dict):
|
||||||
@ -91,26 +94,107 @@ def _get_action(hass, config):
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
def _process_if(hass, config, if_configs, action):
|
def _migrate_old_config(config):
|
||||||
|
""" Migrate old config to new. """
|
||||||
|
if CONF_PLATFORM not in config:
|
||||||
|
return config
|
||||||
|
|
||||||
|
_LOGGER.warning(
|
||||||
|
'You are using an old configuration format. Please upgrade: '
|
||||||
|
'https://home-assistant.io/components/automation.html')
|
||||||
|
|
||||||
|
new_conf = {
|
||||||
|
CONF_TRIGGER: dict(config),
|
||||||
|
CONF_CONDITION: config.get('if', []),
|
||||||
|
CONF_ACTION: dict(config),
|
||||||
|
}
|
||||||
|
|
||||||
|
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
|
||||||
|
('trigger', 'mqtt_payload', 'payload'),
|
||||||
|
('trigger', 'state_entity_id', 'entity_id'),
|
||||||
|
('trigger', 'state_before', 'before'),
|
||||||
|
('trigger', 'state_after', 'after'),
|
||||||
|
('trigger', 'state_to', 'to'),
|
||||||
|
('trigger', 'state_from', 'from'),
|
||||||
|
('trigger', 'state_hours', 'hours'),
|
||||||
|
('trigger', 'state_minutes', 'minutes'),
|
||||||
|
('trigger', 'state_seconds', 'seconds')):
|
||||||
|
if key in new_conf[cat]:
|
||||||
|
new_conf[cat][new_key] = new_conf[cat].pop(key)
|
||||||
|
|
||||||
|
return new_conf
|
||||||
|
|
||||||
|
|
||||||
|
def _process_if(hass, config, p_config, action):
|
||||||
""" Processes if checks. """
|
""" Processes if checks. """
|
||||||
|
|
||||||
|
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
||||||
|
DEFAULT_CONDITION_TYPE).lower()
|
||||||
|
|
||||||
|
if_configs = p_config.get(CONF_CONDITION)
|
||||||
|
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
|
||||||
|
|
||||||
|
if use_trigger:
|
||||||
|
if_configs = p_config[CONF_TRIGGER]
|
||||||
|
|
||||||
if isinstance(if_configs, dict):
|
if isinstance(if_configs, dict):
|
||||||
if_configs = [if_configs]
|
if_configs = [if_configs]
|
||||||
|
|
||||||
|
checks = []
|
||||||
for if_config in if_configs:
|
for if_config in if_configs:
|
||||||
p_type = if_config.get(CONF_PLATFORM)
|
platform = _resolve_platform('if_action', hass, config,
|
||||||
if p_type is None:
|
if_config.get(CONF_PLATFORM))
|
||||||
_LOGGER.error("No platform defined found for if-statement %s",
|
if platform is None:
|
||||||
if_config)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
|
check = platform.if_action(hass, if_config)
|
||||||
|
|
||||||
if platform is None or not hasattr(platform, 'if_action'):
|
# Invalid conditions are allowed if we base it on trigger
|
||||||
_LOGGER.error("Unsupported if-statement platform specified: %s",
|
if check is None and not use_trigger:
|
||||||
p_type)
|
return None
|
||||||
|
|
||||||
|
checks.append(check)
|
||||||
|
|
||||||
|
if cond_type == CONDITION_TYPE_AND:
|
||||||
|
def if_action():
|
||||||
|
""" AND all conditions. """
|
||||||
|
if all(check() for check in checks):
|
||||||
|
action()
|
||||||
|
else:
|
||||||
|
def if_action():
|
||||||
|
""" OR all conditions. """
|
||||||
|
if any(check() for check in checks):
|
||||||
|
action()
|
||||||
|
|
||||||
|
return if_action
|
||||||
|
|
||||||
|
|
||||||
|
def _process_trigger(hass, config, trigger_configs, name, action):
|
||||||
|
""" Setup triggers. """
|
||||||
|
if isinstance(trigger_configs, dict):
|
||||||
|
trigger_configs = [trigger_configs]
|
||||||
|
|
||||||
|
for conf in trigger_configs:
|
||||||
|
platform = _resolve_platform('trigger', hass, config,
|
||||||
|
conf.get(CONF_PLATFORM))
|
||||||
|
if platform is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
action = platform.if_action(hass, if_config, action)
|
if platform.trigger(hass, conf, action):
|
||||||
|
_LOGGER.info("Initialized rule %s", name)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Error setting up rule %s", name)
|
||||||
|
|
||||||
return action
|
|
||||||
|
def _resolve_platform(method, hass, config, platform):
|
||||||
|
""" Find automation platform. """
|
||||||
|
if platform is None:
|
||||||
|
return None
|
||||||
|
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
|
||||||
|
|
||||||
|
if platform is None or not hasattr(platform, method):
|
||||||
|
_LOGGER.error("Unknown automation platform specified for %s: %s",
|
||||||
|
method, platform)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return platform
|
||||||
|
@ -10,8 +10,8 @@ import homeassistant.components.mqtt as mqtt
|
|||||||
|
|
||||||
DEPENDENCIES = ['mqtt']
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
CONF_TOPIC = 'mqtt_topic'
|
CONF_TOPIC = 'topic'
|
||||||
CONF_PAYLOAD = 'mqtt_payload'
|
CONF_PAYLOAD = 'payload'
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
|
@ -9,9 +9,9 @@ import logging
|
|||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
|
|
||||||
|
|
||||||
CONF_ENTITY_ID = "state_entity_id"
|
CONF_ENTITY_ID = "entity_id"
|
||||||
CONF_BELOW = "state_below"
|
CONF_BELOW = "below"
|
||||||
CONF_ABOVE = "state_above"
|
CONF_ABOVE = "above"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -48,14 +48,14 @@ def trigger(hass, config, action):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config, action):
|
def if_action(hass, config):
|
||||||
""" Wraps action method with state based condition. """
|
""" Wraps action method with state based condition. """
|
||||||
|
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
|
||||||
if entity_id is None:
|
if entity_id is None:
|
||||||
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
|
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
|
||||||
return action
|
return None
|
||||||
|
|
||||||
below = config.get(CONF_BELOW)
|
below = config.get(CONF_BELOW)
|
||||||
above = config.get(CONF_ABOVE)
|
above = config.get(CONF_ABOVE)
|
||||||
@ -64,16 +64,14 @@ def if_action(hass, config, action):
|
|||||||
_LOGGER.error("Missing configuration key."
|
_LOGGER.error("Missing configuration key."
|
||||||
" One of %s or %s is required",
|
" One of %s or %s is required",
|
||||||
CONF_BELOW, CONF_ABOVE)
|
CONF_BELOW, CONF_ABOVE)
|
||||||
return action
|
return None
|
||||||
|
|
||||||
def state_if():
|
|
||||||
""" Execute action if state matches. """
|
|
||||||
|
|
||||||
|
def if_numeric_state():
|
||||||
|
""" Test numeric state condition. """
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
if state is None or _in_range(state.state, above, below):
|
return state is not None and _in_range(state.state, above, below)
|
||||||
action()
|
|
||||||
|
|
||||||
return state_if
|
return if_numeric_state
|
||||||
|
|
||||||
|
|
||||||
def _in_range(value, range_start, range_end):
|
def _in_range(value, range_start, range_end):
|
||||||
|
@ -10,9 +10,9 @@ from homeassistant.helpers.event import track_state_change
|
|||||||
from homeassistant.const import MATCH_ALL
|
from homeassistant.const import MATCH_ALL
|
||||||
|
|
||||||
|
|
||||||
CONF_ENTITY_ID = "state_entity_id"
|
CONF_ENTITY_ID = "entity_id"
|
||||||
CONF_FROM = "state_from"
|
CONF_FROM = "from"
|
||||||
CONF_TO = "state_to"
|
CONF_TO = "to"
|
||||||
CONF_STATE = "state"
|
CONF_STATE = "state"
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ def trigger(hass, config, action):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||||
to_state = config.get(CONF_TO, MATCH_ALL)
|
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
|
||||||
|
|
||||||
def state_automation_listener(entity, from_s, to_s):
|
def state_automation_listener(entity, from_s, to_s):
|
||||||
""" Listens for state changes and calls action. """
|
""" Listens for state changes and calls action. """
|
||||||
@ -38,7 +38,7 @@ def trigger(hass, config, action):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config, action):
|
def if_action(hass, config):
|
||||||
""" Wraps action method with state based condition. """
|
""" Wraps action method with state based condition. """
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
state = config.get(CONF_STATE)
|
state = config.get(CONF_STATE)
|
||||||
@ -47,11 +47,12 @@ def if_action(hass, config, action):
|
|||||||
logging.getLogger(__name__).error(
|
logging.getLogger(__name__).error(
|
||||||
"Missing if-condition configuration key %s or %s", CONF_ENTITY_ID,
|
"Missing if-condition configuration key %s or %s", CONF_ENTITY_ID,
|
||||||
CONF_STATE)
|
CONF_STATE)
|
||||||
return action
|
return None
|
||||||
|
|
||||||
def state_if():
|
state = str(state)
|
||||||
""" Execute action if state matches. """
|
|
||||||
if hass.states.is_state(entity_id, state):
|
|
||||||
action()
|
|
||||||
|
|
||||||
return state_if
|
def if_state():
|
||||||
|
""" Test if condition. """
|
||||||
|
return hass.states.is_state(entity_id, state)
|
||||||
|
|
||||||
|
return if_state
|
||||||
|
103
homeassistant/components/automation/sun.py
Normal file
103
homeassistant/components/automation/sun.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.automation.sun
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Offers sun based automation rules.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.components import sun
|
||||||
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
DEPENDENCIES = ['sun']
|
||||||
|
|
||||||
|
CONF_OFFSET = 'offset'
|
||||||
|
CONF_EVENT = 'event'
|
||||||
|
|
||||||
|
EVENT_SUNSET = 'sunset'
|
||||||
|
EVENT_SUNRISE = 'sunrise'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def trigger(hass, config, action):
|
||||||
|
""" Listen for events based on config. """
|
||||||
|
event = config.get(CONF_EVENT)
|
||||||
|
|
||||||
|
if event is None:
|
||||||
|
_LOGGER.error("Missing configuration key %s", CONF_EVENT)
|
||||||
|
return False
|
||||||
|
|
||||||
|
event = event.lower()
|
||||||
|
if event not in (EVENT_SUNRISE, EVENT_SUNSET):
|
||||||
|
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if CONF_OFFSET in config:
|
||||||
|
raw_offset = config.get(CONF_OFFSET)
|
||||||
|
|
||||||
|
negative_offset = False
|
||||||
|
if raw_offset.startswith('-'):
|
||||||
|
negative_offset = True
|
||||||
|
raw_offset = raw_offset[1:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.error('Could not parse offset %s', raw_offset)
|
||||||
|
return False
|
||||||
|
|
||||||
|
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||||
|
|
||||||
|
if negative_offset:
|
||||||
|
offset *= -1
|
||||||
|
else:
|
||||||
|
offset = timedelta(0)
|
||||||
|
|
||||||
|
# Do something to call action
|
||||||
|
if event == EVENT_SUNRISE:
|
||||||
|
trigger_sunrise(hass, action, offset)
|
||||||
|
else:
|
||||||
|
trigger_sunset(hass, action, offset)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_sunrise(hass, action, offset):
|
||||||
|
""" Trigger action at next sun rise. """
|
||||||
|
def next_rise():
|
||||||
|
""" Returns next sunrise. """
|
||||||
|
next_time = sun.next_rising_utc(hass) + offset
|
||||||
|
|
||||||
|
while next_time < dt_util.utcnow():
|
||||||
|
next_time = next_time + timedelta(days=1)
|
||||||
|
|
||||||
|
return next_time
|
||||||
|
|
||||||
|
def sunrise_automation_listener(now):
|
||||||
|
""" Called when it's time for action. """
|
||||||
|
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
||||||
|
action()
|
||||||
|
|
||||||
|
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_sunset(hass, action, offset):
|
||||||
|
""" Trigger action at next sun set. """
|
||||||
|
def next_set():
|
||||||
|
""" Returns next sunrise. """
|
||||||
|
next_time = sun.next_setting_utc(hass) + offset
|
||||||
|
|
||||||
|
while next_time < dt_util.utcnow():
|
||||||
|
next_time = next_time + timedelta(days=1)
|
||||||
|
|
||||||
|
return next_time
|
||||||
|
|
||||||
|
def sunset_automation_listener(now):
|
||||||
|
""" Called when it's time for action. """
|
||||||
|
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||||
|
action()
|
||||||
|
|
||||||
|
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
@ -10,21 +10,35 @@ from homeassistant.util import convert
|
|||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.event import track_time_change
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
|
||||||
CONF_HOURS = "time_hours"
|
CONF_HOURS = "hours"
|
||||||
CONF_MINUTES = "time_minutes"
|
CONF_MINUTES = "minutes"
|
||||||
CONF_SECONDS = "time_seconds"
|
CONF_SECONDS = "seconds"
|
||||||
CONF_BEFORE = "before"
|
CONF_BEFORE = "before"
|
||||||
CONF_AFTER = "after"
|
CONF_AFTER = "after"
|
||||||
CONF_WEEKDAY = "weekday"
|
CONF_WEEKDAY = "weekday"
|
||||||
|
|
||||||
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for state changes based on `config`. """
|
""" Listen for state changes based on `config`. """
|
||||||
hours = convert(config.get(CONF_HOURS), int)
|
if CONF_AFTER in config:
|
||||||
minutes = convert(config.get(CONF_MINUTES), int)
|
after = dt_util.parse_time_str(config[CONF_AFTER])
|
||||||
seconds = convert(config.get(CONF_SECONDS), int)
|
if after is None:
|
||||||
|
_error_time(config[CONF_AFTER], CONF_AFTER)
|
||||||
|
return False
|
||||||
|
hours, minutes, seconds = after.hour, after.minute, after.second
|
||||||
|
elif (CONF_HOURS in config or CONF_MINUTES in config
|
||||||
|
or CONF_SECONDS in config):
|
||||||
|
hours = convert(config.get(CONF_HOURS), int)
|
||||||
|
minutes = convert(config.get(CONF_MINUTES), int)
|
||||||
|
seconds = convert(config.get(CONF_SECONDS), int)
|
||||||
|
else:
|
||||||
|
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
|
||||||
|
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
|
||||||
|
return False
|
||||||
|
|
||||||
def time_automation_listener(now):
|
def time_automation_listener(now):
|
||||||
""" Listens for time changes and calls action. """
|
""" Listens for time changes and calls action. """
|
||||||
@ -36,7 +50,7 @@ def trigger(hass, config, action):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config, action):
|
def if_action(hass, config):
|
||||||
""" Wraps action method with time based condition. """
|
""" Wraps action method with time based condition. """
|
||||||
before = config.get(CONF_BEFORE)
|
before = config.get(CONF_BEFORE)
|
||||||
after = config.get(CONF_AFTER)
|
after = config.get(CONF_AFTER)
|
||||||
@ -46,37 +60,46 @@ def if_action(hass, config, action):
|
|||||||
logging.getLogger(__name__).error(
|
logging.getLogger(__name__).error(
|
||||||
"Missing if-condition configuration key %s, %s or %s",
|
"Missing if-condition configuration key %s, %s or %s",
|
||||||
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if before is not None:
|
||||||
|
before = dt_util.parse_time_str(before)
|
||||||
|
if before is None:
|
||||||
|
_error_time(before, CONF_BEFORE)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if after is not None:
|
||||||
|
after = dt_util.parse_time_str(after)
|
||||||
|
if after is None:
|
||||||
|
_error_time(after, CONF_AFTER)
|
||||||
|
return None
|
||||||
|
|
||||||
def time_if():
|
def time_if():
|
||||||
""" Validate time based if-condition """
|
""" Validate time based if-condition """
|
||||||
now = dt_util.now()
|
now = dt_util.now()
|
||||||
|
if before is not None and now > now.replace(hour=before.hour,
|
||||||
|
minute=before.minute):
|
||||||
|
return False
|
||||||
|
|
||||||
if before is not None:
|
if after is not None and now < now.replace(hour=after.hour,
|
||||||
# Strip seconds if given
|
minute=after.minute):
|
||||||
before_h, before_m = before.split(':')[0:2]
|
return False
|
||||||
|
|
||||||
before_point = now.replace(hour=int(before_h),
|
|
||||||
minute=int(before_m))
|
|
||||||
|
|
||||||
if now > before_point:
|
|
||||||
return
|
|
||||||
|
|
||||||
if after is not None:
|
|
||||||
# Strip seconds if given
|
|
||||||
after_h, after_m = after.split(':')[0:2]
|
|
||||||
|
|
||||||
after_point = now.replace(hour=int(after_h), minute=int(after_m))
|
|
||||||
|
|
||||||
if now < after_point:
|
|
||||||
return
|
|
||||||
|
|
||||||
if weekday is not None:
|
if weekday is not None:
|
||||||
now_weekday = WEEKDAYS[now.weekday()]
|
now_weekday = WEEKDAYS[now.weekday()]
|
||||||
|
|
||||||
if isinstance(weekday, str) and weekday != now_weekday or \
|
if isinstance(weekday, str) and weekday != now_weekday or \
|
||||||
now_weekday not in weekday:
|
now_weekday not in weekday:
|
||||||
return
|
return False
|
||||||
|
|
||||||
action()
|
return True
|
||||||
|
|
||||||
return time_if
|
return time_if
|
||||||
|
|
||||||
|
|
||||||
|
def _error_time(value, key):
|
||||||
|
""" Helper method to print error. """
|
||||||
|
_LOGGER.error(
|
||||||
|
"Received invalid value for '%s': %s", key, value)
|
||||||
|
if isinstance(value, int):
|
||||||
|
_LOGGER.error('Make sure you wrap time values in quotes')
|
||||||
|
105
homeassistant/components/camera/foscam.py
Normal file
105
homeassistant/components/camera/foscam.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.camera.foscam
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
This component provides basic support for Foscam IP cameras.
|
||||||
|
|
||||||
|
As part of the basic support the following features will be provided:
|
||||||
|
-MJPEG video streaming
|
||||||
|
|
||||||
|
To use this component, add the following to your configuration.yaml file.
|
||||||
|
|
||||||
|
camera:
|
||||||
|
platform: foscam
|
||||||
|
name: Door Camera
|
||||||
|
ip: 192.168.0.123
|
||||||
|
port: 88
|
||||||
|
username: YOUR_USERNAME
|
||||||
|
password: YOUR_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
ip
|
||||||
|
*Required
|
||||||
|
The IP address of your Foscam device.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of a visitor or operator of your camera. Oddly admin accounts
|
||||||
|
don't seem to have access to take snapshots.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for accessing your camera.
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
This parameter allows you to override the name of your camera in homeassistant.
|
||||||
|
|
||||||
|
port
|
||||||
|
*Optional
|
||||||
|
The port that the camera is running on. The default is 88.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/camera.foscam.html
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.camera import DOMAIN
|
||||||
|
from homeassistant.components.camera import Camera
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Adds a Foscam IP Camera. """
|
||||||
|
if not validate_config({DOMAIN: config},
|
||||||
|
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
add_devices_callback([FoscamCamera(config)])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
class FoscamCamera(Camera):
|
||||||
|
""" An implementation of a Foscam IP camera. """
|
||||||
|
|
||||||
|
def __init__(self, device_info):
|
||||||
|
super(FoscamCamera, self).__init__()
|
||||||
|
|
||||||
|
ip_address = device_info.get('ip')
|
||||||
|
port = device_info.get('port', 88)
|
||||||
|
|
||||||
|
self._base_url = 'http://' + ip_address + ':' + str(port) + '/'
|
||||||
|
self._username = device_info.get('username')
|
||||||
|
self._password = device_info.get('password')
|
||||||
|
self._snap_picture_url = self._base_url \
|
||||||
|
+ 'cgi-bin/CGIProxy.fcgi?cmd=snapPicture&usr=' \
|
||||||
|
+ self._username + '&pwd=' + self._password
|
||||||
|
self._name = device_info.get('name', 'Foscam Camera')
|
||||||
|
|
||||||
|
_LOGGER.info('Using the following URL for %s: %s',
|
||||||
|
self._name, self._snap_picture_url)
|
||||||
|
|
||||||
|
def camera_image(self):
|
||||||
|
""" Return a still image reponse from the camera. """
|
||||||
|
|
||||||
|
# send the request to snap a picture
|
||||||
|
response = requests.get(self._snap_picture_url)
|
||||||
|
|
||||||
|
# parse the response to find the image file name
|
||||||
|
|
||||||
|
pattern = re.compile('src="[.][.]/(.*[.]jpg)"')
|
||||||
|
filename = pattern.search(response.content.decode("utf-8")).group(1)
|
||||||
|
|
||||||
|
# send request for the image
|
||||||
|
response = requests.get(self._base_url + filename)
|
||||||
|
|
||||||
|
return response.content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Return the name of this device. """
|
||||||
|
return self._name
|
@ -176,10 +176,8 @@ class DeviceTracker(object):
|
|||||||
self.track_new = track_new
|
self.track_new = track_new
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
entity_ids = []
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device.track:
|
if device.track:
|
||||||
entity_ids.append(device.entity_id)
|
|
||||||
device.update_ha_state()
|
device.update_ha_state()
|
||||||
|
|
||||||
self.group = None
|
self.group = None
|
||||||
@ -194,9 +192,9 @@ class DeviceTracker(object):
|
|||||||
mac = mac.upper()
|
mac = mac.upper()
|
||||||
device = self.mac_to_dev.get(mac)
|
device = self.mac_to_dev.get(mac)
|
||||||
if not device:
|
if not device:
|
||||||
dev_id = util.slugify(host_name or mac)
|
dev_id = util.slugify(host_name or '') or util.slugify(mac)
|
||||||
else:
|
else:
|
||||||
dev_id = str(dev_id)
|
dev_id = str(dev_id).lower()
|
||||||
device = self.devices.get(dev_id)
|
device = self.devices.get(dev_id)
|
||||||
|
|
||||||
if device:
|
if device:
|
||||||
@ -234,7 +232,8 @@ class DeviceTracker(object):
|
|||||||
""" Update stale devices. """
|
""" Update stale devices. """
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for device in self.devices.values():
|
for device in self.devices.values():
|
||||||
if device.last_update_home and device.stale(now):
|
if (device.track and device.last_update_home and
|
||||||
|
device.stale(now)):
|
||||||
device.update_ha_state(True)
|
device.update_ha_state(True)
|
||||||
|
|
||||||
|
|
||||||
@ -336,7 +335,8 @@ def convert_csv_config(csv_path, yaml_path):
|
|||||||
with open(csv_path) as inp:
|
with open(csv_path) as inp:
|
||||||
for row in csv.DictReader(inp):
|
for row in csv.DictReader(inp):
|
||||||
dev_id = util.ensure_unique_string(
|
dev_id = util.ensure_unique_string(
|
||||||
util.slugify(row['name']) or DEVICE_DEFAULT_NAME, used_ids)
|
(util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(),
|
||||||
|
used_ids)
|
||||||
used_ids.add(dev_id)
|
used_ids.add(dev_id)
|
||||||
device = Device(None, None, row['track'] == '1', dev_id,
|
device = Device(None, None, row['track'] == '1', dev_id,
|
||||||
row['device'], row['name'], row['picture'])
|
row['device'], row['name'], row['picture'])
|
||||||
@ -350,8 +350,9 @@ def load_config(path, hass, consider_home):
|
|||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
Device(hass, consider_home, device.get('track', False),
|
Device(hass, consider_home, device.get('track', False),
|
||||||
str(dev_id), device.get('mac'), device.get('name'),
|
str(dev_id).lower(), str(device.get('mac')).upper(),
|
||||||
device.get('picture'), device.get(CONF_AWAY_HIDE, False))
|
device.get('name'), device.get('picture'),
|
||||||
|
device.get(CONF_AWAY_HIDE, False))
|
||||||
for dev_id, device in load_yaml_config_file(path).items()]
|
for dev_id, device in load_yaml_config_file(path).items()]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "397aa7c09f4938b1358672c9983f9f32"
|
VERSION = "0ab148ece11ddde26b95460c2c91da3d"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 9637d5d26516873b8a04a3c62b9596163c822a2d
|
Subproject commit 63e039a221ae6771e0d7c6990d9a93b7cc22fc64
|
@ -484,7 +484,7 @@ class MediaPlayerDevice(Entity):
|
|||||||
else:
|
else:
|
||||||
state_attr = {
|
state_attr = {
|
||||||
attr: getattr(self, attr) for attr
|
attr: getattr(self, attr) for attr
|
||||||
in ATTR_TO_PROPERTY if getattr(self, attr)
|
in ATTR_TO_PROPERTY if getattr(self, attr) is not None
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.media_image_url:
|
if self.media_image_url:
|
||||||
|
@ -356,7 +356,8 @@ class AirPlayDevice(MediaPlayerDevice):
|
|||||||
self.player_state = state_hash.get('player_state', None)
|
self.player_state = state_hash.get('player_state', None)
|
||||||
|
|
||||||
if 'name' in state_hash:
|
if 'name' in state_hash:
|
||||||
self.device_name = state_hash.get('name', 'AirPlay')
|
name = state_hash.get('name', '')
|
||||||
|
self.device_name = (name + ' AirTunes Speaker').strip()
|
||||||
|
|
||||||
if 'kind' in state_hash:
|
if 'kind' in state_hash:
|
||||||
self.kind = state_hash.get('kind', None)
|
self.kind = state_hash.get('kind', None)
|
||||||
|
@ -28,6 +28,14 @@ REQUIREMENTS = ['SoCo==0.11.1']
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# The soco library is excessively chatty when it comes to logging and
|
||||||
|
# causes a LOT of spam in the logs due to making a http connection to each
|
||||||
|
# speaker every 10 seconds. Quiet it down a bit to just actual problems.
|
||||||
|
_SOCO_LOGGER = logging.getLogger('soco')
|
||||||
|
_SOCO_LOGGER.setLevel(logging.ERROR)
|
||||||
|
_REQUESTS_LOGGER = logging.getLogger('requests')
|
||||||
|
_REQUESTS_LOGGER.setLevel(logging.ERROR)
|
||||||
|
|
||||||
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ class Recorder(threading.Thread):
|
|||||||
""" Query the database. """
|
""" Query the database. """
|
||||||
try:
|
try:
|
||||||
with self.conn, self.lock:
|
with self.conn, self.lock:
|
||||||
_LOGGER.info("Running query %s", sql_query)
|
_LOGGER.debug("Running query %s", sql_query)
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
|
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
"""
|
|
||||||
homeassistant.components.scheduler
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
A component that will act as a scheduler and perform actions based
|
|
||||||
on the events in the schedule.
|
|
||||||
|
|
||||||
It will read a json object from schedule.json in the config dir
|
|
||||||
and create a schedule based on it.
|
|
||||||
Each schedule is a JSON with the keys id, name, description,
|
|
||||||
entity_ids, and events.
|
|
||||||
- days is an array with the weekday number (monday=0) that the schedule
|
|
||||||
is active
|
|
||||||
- entity_ids an array with entity ids that the events in the schedule should
|
|
||||||
effect (can also be groups)
|
|
||||||
- events is an array of objects that describe the different events that is
|
|
||||||
supported. Read in the events descriptions for more information.
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
|
||||||
from homeassistant.loader import get_component
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
|
||||||
|
|
||||||
DOMAIN = 'scheduler'
|
|
||||||
|
|
||||||
DEPENDENCIES = []
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
_SCHEDULE_FILE = 'schedule.json'
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
|
||||||
""" Create the schedules. """
|
|
||||||
|
|
||||||
def setup_listener(schedule, event_data):
|
|
||||||
""" Creates the event listener based on event_data. """
|
|
||||||
event_type = event_data['type']
|
|
||||||
component = event_type
|
|
||||||
|
|
||||||
# if the event isn't part of a component
|
|
||||||
if event_type in ['time']:
|
|
||||||
component = 'scheduler.{}'.format(event_type)
|
|
||||||
|
|
||||||
elif not bootstrap.setup_component(hass, component, config):
|
|
||||||
_LOGGER.warn("Could setup event listener for %s", component)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return get_component(component).create_event_listener(schedule,
|
|
||||||
event_data)
|
|
||||||
|
|
||||||
def setup_schedule(schedule_data):
|
|
||||||
""" Setup a schedule based on the description. """
|
|
||||||
|
|
||||||
schedule = Schedule(schedule_data['id'],
|
|
||||||
name=schedule_data['name'],
|
|
||||||
description=schedule_data['description'],
|
|
||||||
entity_ids=schedule_data['entity_ids'],
|
|
||||||
days=schedule_data['days'])
|
|
||||||
|
|
||||||
for event_data in schedule_data['events']:
|
|
||||||
event_listener = setup_listener(schedule, event_data)
|
|
||||||
|
|
||||||
if event_listener:
|
|
||||||
schedule.add_event_listener(event_listener)
|
|
||||||
|
|
||||||
schedule.schedule(hass)
|
|
||||||
return True
|
|
||||||
|
|
||||||
with open(hass.config.path(_SCHEDULE_FILE)) as schedule_file:
|
|
||||||
schedule_descriptions = json.load(schedule_file)
|
|
||||||
|
|
||||||
for schedule_description in schedule_descriptions:
|
|
||||||
if not setup_schedule(schedule_description):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Schedule(object):
|
|
||||||
""" A Schedule """
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def __init__(self, schedule_id, name=None, description=None,
|
|
||||||
entity_ids=None, days=None):
|
|
||||||
|
|
||||||
self.schedule_id = schedule_id
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
|
|
||||||
self.entity_ids = entity_ids or []
|
|
||||||
|
|
||||||
self.days = days or [0, 1, 2, 3, 4, 5, 6]
|
|
||||||
|
|
||||||
self.__event_listeners = []
|
|
||||||
|
|
||||||
def add_event_listener(self, event_listener):
|
|
||||||
""" Add a event to the schedule. """
|
|
||||||
self.__event_listeners.append(event_listener)
|
|
||||||
|
|
||||||
def schedule(self, hass):
|
|
||||||
""" Schedule all the events in the schedule. """
|
|
||||||
for event in self.__event_listeners:
|
|
||||||
event.schedule(hass)
|
|
||||||
|
|
||||||
|
|
||||||
class EventListener(object):
|
|
||||||
""" The base EventListener class that the schedule uses. """
|
|
||||||
def __init__(self, schedule):
|
|
||||||
self.my_schedule = schedule
|
|
||||||
|
|
||||||
def schedule(self, hass):
|
|
||||||
""" Schedule the event """
|
|
||||||
pass
|
|
||||||
|
|
||||||
def execute(self, hass):
|
|
||||||
""" execute the event """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class ServiceEventListener(EventListener):
|
|
||||||
""" A EventListener that calls a service when executed. """
|
|
||||||
|
|
||||||
def __init__(self, schdule, service):
|
|
||||||
EventListener.__init__(self, schdule)
|
|
||||||
|
|
||||||
(self.domain, self.service) = service.split('.')
|
|
||||||
|
|
||||||
def execute(self, hass):
|
|
||||||
""" Call the service. """
|
|
||||||
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
|
||||||
hass.services.call(self.domain, self.service, data)
|
|
||||||
|
|
||||||
# Reschedule for next day
|
|
||||||
self.schedule(hass)
|
|
@ -1,70 +0,0 @@
|
|||||||
"""
|
|
||||||
homeassistant.components.scheduler.time
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
An event in the scheduler component that will call the service
|
|
||||||
every specified day at the time specified.
|
|
||||||
A time event need to have the type 'time', which service to call and at
|
|
||||||
which time.
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "time",
|
|
||||||
"service": "switch.turn_off",
|
|
||||||
"time": "22:00:00"
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.helpers.event import track_point_in_time
|
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def create_event_listener(schedule, event_listener_data):
|
|
||||||
""" Create a TimeEvent based on the description. """
|
|
||||||
|
|
||||||
service = event_listener_data['service']
|
|
||||||
(hour, minute, second) = [int(x) for x in
|
|
||||||
event_listener_data['time'].split(':', 3)]
|
|
||||||
|
|
||||||
return TimeEventListener(schedule, service, hour, minute, second)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class TimeEventListener(ServiceEventListener):
|
|
||||||
""" The time event that the scheduler uses. """
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def __init__(self, schedule, service, hour, minute, second):
|
|
||||||
ServiceEventListener.__init__(self, schedule, service)
|
|
||||||
|
|
||||||
self.hour = hour
|
|
||||||
self.minute = minute
|
|
||||||
self.second = second
|
|
||||||
|
|
||||||
def schedule(self, hass):
|
|
||||||
""" Schedule this event so that it will be called. """
|
|
||||||
|
|
||||||
next_time = dt_util.now().replace(
|
|
||||||
hour=self.hour, minute=self.minute, second=self.second)
|
|
||||||
|
|
||||||
# Calculate the next time the event should be executed.
|
|
||||||
# That is the next day that the schedule is configured to run
|
|
||||||
while next_time < dt_util.now() or \
|
|
||||||
next_time.weekday() not in self.my_schedule.days:
|
|
||||||
|
|
||||||
next_time = next_time + timedelta(days=1)
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def execute(now):
|
|
||||||
""" Call the execute method """
|
|
||||||
self.execute(hass)
|
|
||||||
|
|
||||||
track_point_in_time(hass, execute, next_time)
|
|
||||||
|
|
||||||
_LOGGER.info(
|
|
||||||
'TimeEventListener scheduled for %s, will call service %s.%s',
|
|
||||||
next_time, self.domain, self.service)
|
|
@ -3,7 +3,6 @@ homeassistant.components.sensor.glances
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Gathers system information of hosts which running glances.
|
Gathers system information of hosts which running glances.
|
||||||
|
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
To use the glances sensor you will need to add something like the following
|
To use the glances sensor you will need to add something like the following
|
||||||
|
@ -91,7 +91,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Connection error "
|
"Connection error "
|
||||||
"Please check your settings for OpenWeatherMap.")
|
"Please check your settings for OpenWeatherMap.")
|
||||||
return None
|
return False
|
||||||
|
|
||||||
data = WeatherData(owm, forecast, hass.config.latitude,
|
data = WeatherData(owm, forecast, hass.config.latitude,
|
||||||
hass.config.longitude)
|
hass.config.longitude)
|
||||||
|
@ -36,12 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
hasattr(value, 'humidity') and value.humidity
|
hasattr(value, 'humidity') and value.humidity
|
||||||
])
|
])
|
||||||
|
|
||||||
sensors.extend([
|
|
||||||
VerisureAlarm(value)
|
|
||||||
for value in verisure.get_alarm_status().values()
|
|
||||||
if verisure.SHOW_ALARM
|
|
||||||
])
|
|
||||||
|
|
||||||
add_devices(sensors)
|
add_devices(sensors)
|
||||||
|
|
||||||
|
|
||||||
@ -103,25 +97,3 @@ class VerisureHygrometer(Entity):
|
|||||||
def update(self):
|
def update(self):
|
||||||
''' update sensor '''
|
''' update sensor '''
|
||||||
verisure.update()
|
verisure.update()
|
||||||
|
|
||||||
|
|
||||||
class VerisureAlarm(Entity):
|
|
||||||
""" represents a Verisure alarm status within home assistant. """
|
|
||||||
|
|
||||||
def __init__(self, alarm_status):
|
|
||||||
self._id = alarm_status.id
|
|
||||||
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
""" Returns the name of the device. """
|
|
||||||
return 'Alarm {}'.format(self._id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
""" Returns the state of the device. """
|
|
||||||
return verisure.STATUS[self._device][self._id].label
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
''' update sensor '''
|
|
||||||
verisure.update()
|
|
||||||
|
@ -25,10 +25,8 @@ import urllib
|
|||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
track_point_in_utc_time, track_point_in_time)
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['astral==0.8.1']
|
REQUIREMENTS = ['astral==0.8.1']
|
||||||
@ -214,95 +212,3 @@ class Sun(Entity):
|
|||||||
track_point_in_utc_time(
|
track_point_in_utc_time(
|
||||||
self.hass, self.point_in_time_listener,
|
self.hass, self.point_in_time_listener,
|
||||||
self.next_change + timedelta(seconds=1))
|
self.next_change + timedelta(seconds=1))
|
||||||
|
|
||||||
|
|
||||||
def create_event_listener(schedule, event_listener_data):
|
|
||||||
""" Create a sun event listener based on the description. """
|
|
||||||
|
|
||||||
negative_offset = False
|
|
||||||
service = event_listener_data['service']
|
|
||||||
offset_str = event_listener_data['offset']
|
|
||||||
event = event_listener_data['event']
|
|
||||||
|
|
||||||
if offset_str.startswith('-'):
|
|
||||||
negative_offset = True
|
|
||||||
offset_str = offset_str[1:]
|
|
||||||
|
|
||||||
(hour, minute, second) = [int(x) for x in offset_str.split(':')]
|
|
||||||
|
|
||||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
|
||||||
|
|
||||||
if event == 'sunset':
|
|
||||||
return SunsetEventListener(schedule, service, offset, negative_offset)
|
|
||||||
|
|
||||||
return SunriseEventListener(schedule, service, offset, negative_offset)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class SunEventListener(ServiceEventListener):
|
|
||||||
""" This is the base class for sun event listeners. """
|
|
||||||
|
|
||||||
def __init__(self, schedule, service, offset, negative_offset):
|
|
||||||
ServiceEventListener.__init__(self, schedule, service)
|
|
||||||
|
|
||||||
self.offset = offset
|
|
||||||
self.negative_offset = negative_offset
|
|
||||||
|
|
||||||
def __get_next_time(self, next_event):
|
|
||||||
"""
|
|
||||||
Returns when the next time the service should be called.
|
|
||||||
Taking into account the offset and which days the event should execute.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.negative_offset:
|
|
||||||
next_time = next_event - self.offset
|
|
||||||
else:
|
|
||||||
next_time = next_event + self.offset
|
|
||||||
|
|
||||||
while next_time < dt_util.now() or \
|
|
||||||
next_time.weekday() not in self.my_schedule.days:
|
|
||||||
next_time = next_time + timedelta(days=1)
|
|
||||||
|
|
||||||
return next_time
|
|
||||||
|
|
||||||
def schedule_next_event(self, hass, next_event):
|
|
||||||
""" Schedule the event. """
|
|
||||||
next_time = self.__get_next_time(next_event)
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def execute(now):
|
|
||||||
""" Call the execute method. """
|
|
||||||
self.execute(hass)
|
|
||||||
|
|
||||||
track_point_in_time(hass, execute, next_time)
|
|
||||||
|
|
||||||
return next_time
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class SunsetEventListener(SunEventListener):
|
|
||||||
""" This class is used the call a service when the sun sets. """
|
|
||||||
def schedule(self, hass):
|
|
||||||
""" Schedule the event """
|
|
||||||
next_setting_dt = next_setting(hass)
|
|
||||||
|
|
||||||
next_time_dt = self.schedule_next_event(hass, next_setting_dt)
|
|
||||||
|
|
||||||
_LOGGER.info(
|
|
||||||
'SunsetEventListener scheduled for %s, will call service %s.%s',
|
|
||||||
next_time_dt, self.domain, self.service)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class SunriseEventListener(SunEventListener):
|
|
||||||
""" This class is used the call a service when the sun rises. """
|
|
||||||
|
|
||||||
def schedule(self, hass):
|
|
||||||
""" Schedule the event. """
|
|
||||||
next_rising_dt = next_rising(hass)
|
|
||||||
|
|
||||||
next_time_dt = self.schedule_next_event(hass, next_rising_dt)
|
|
||||||
|
|
||||||
_LOGGER.info(
|
|
||||||
'SunriseEventListener scheduled for %s, will call service %s.%s',
|
|
||||||
next_time_dt, self.domain, self.service)
|
|
||||||
|
@ -59,8 +59,9 @@ from homeassistant.const import (
|
|||||||
DOMAIN = "verisure"
|
DOMAIN = "verisure"
|
||||||
DISCOVER_SENSORS = 'verisure.sensors'
|
DISCOVER_SENSORS = 'verisure.sensors'
|
||||||
DISCOVER_SWITCHES = 'verisure.switches'
|
DISCOVER_SWITCHES = 'verisure.switches'
|
||||||
|
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = ['alarm_control_panel']
|
||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
'https://github.com/persandstrom/python-verisure/archive/'
|
'https://github.com/persandstrom/python-verisure/archive/'
|
||||||
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
|
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
|
||||||
@ -123,7 +124,8 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# Load components for the devices in the ISY controller that we support
|
# Load components for the devices in the ISY controller that we support
|
||||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||||
('switch', DISCOVER_SWITCHES))):
|
('switch', DISCOVER_SWITCHES),
|
||||||
|
('alarm_control_panel', DISCOVER_ALARMS))):
|
||||||
component = get_component(comp_name)
|
component = get_component(comp_name)
|
||||||
_LOGGER.info(config[DOMAIN])
|
_LOGGER.info(config[DOMAIN])
|
||||||
bootstrap.setup_component(hass, component.DOMAIN, config)
|
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||||
@ -166,7 +168,7 @@ def reconnect():
|
|||||||
def update():
|
def update():
|
||||||
""" Updates the status of verisure components. """
|
""" Updates the status of verisure components. """
|
||||||
if WRONG_PASSWORD_GIVEN:
|
if WRONG_PASSWORD_GIVEN:
|
||||||
# Is there any way to inform user?
|
_LOGGER.error('Wrong password')
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -47,6 +47,9 @@ STATE_PLAYING = 'playing'
|
|||||||
STATE_PAUSED = 'paused'
|
STATE_PAUSED = 'paused'
|
||||||
STATE_IDLE = 'idle'
|
STATE_IDLE = 'idle'
|
||||||
STATE_STANDBY = 'standby'
|
STATE_STANDBY = 'standby'
|
||||||
|
STATE_ALARM_DISARMED = 'disarmed'
|
||||||
|
STATE_ALARM_ARMED_HOME = 'armed_home'
|
||||||
|
STATE_ALARM_ARMED_AWAY = 'armed_away'
|
||||||
|
|
||||||
# #### STATE AND EVENT ATTRIBUTES ####
|
# #### STATE AND EVENT ATTRIBUTES ####
|
||||||
# Contains current time for a TIME_CHANGED event
|
# Contains current time for a TIME_CHANGED event
|
||||||
@ -114,6 +117,10 @@ SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
|||||||
SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track"
|
SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track"
|
||||||
SERVICE_MEDIA_SEEK = "media_seek"
|
SERVICE_MEDIA_SEEK = "media_seek"
|
||||||
|
|
||||||
|
SERVICE_ALARM_DISARM = "alarm_disarm"
|
||||||
|
SERVICE_ALARM_ARM_HOME = "alarm_arm_home"
|
||||||
|
SERVICE_ALARM_ARM_AWAY = "alarm_arm_away"
|
||||||
|
|
||||||
# #### API / REMOTE ####
|
# #### API / REMOTE ####
|
||||||
SERVER_PORT = 8123
|
SERVER_PORT = 8123
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ def reproduce_state(hass, states, blocking=False):
|
|||||||
current_state = hass.states.get(state.entity_id)
|
current_state = hass.states.get(state.entity_id)
|
||||||
|
|
||||||
if current_state is None:
|
if current_state is None:
|
||||||
|
_LOGGER.warning('reproduce_state: Unable to find entity %s',
|
||||||
|
state.entity_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if state.state == STATE_ON:
|
if state.state == STATE_ON:
|
||||||
@ -58,7 +60,8 @@ def reproduce_state(hass, states, blocking=False):
|
|||||||
elif state.state == STATE_OFF:
|
elif state.state == STATE_OFF:
|
||||||
service = SERVICE_TURN_OFF
|
service = SERVICE_TURN_OFF
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Unable to reproduce state for %s", state)
|
_LOGGER.warning("reproduce_state: Unable to reproduce state %s",
|
||||||
|
state)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
service_data = dict(state.attributes)
|
service_data = dict(state.attributes)
|
||||||
|
@ -21,7 +21,7 @@ from .dt import datetime_to_local_str, utcnow
|
|||||||
|
|
||||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||||
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
||||||
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
RE_SLUGIFY = re.compile(r'[^a-z0-9_]+')
|
||||||
|
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
@ -36,7 +36,7 @@ def sanitize_path(path):
|
|||||||
|
|
||||||
def slugify(text):
|
def slugify(text):
|
||||||
""" Slugifies a given text. """
|
""" Slugifies a given text. """
|
||||||
text = text.replace(" ", "_")
|
text = text.lower().replace(" ", "_")
|
||||||
|
|
||||||
return RE_SLUGIFY.sub("", text)
|
return RE_SLUGIFY.sub("", text)
|
||||||
|
|
||||||
|
@ -131,3 +131,20 @@ def date_str_to_date(dt_str):
|
|||||||
def strip_microseconds(dattim):
|
def strip_microseconds(dattim):
|
||||||
""" Returns a copy of dattime object but with microsecond set to 0. """
|
""" Returns a copy of dattime object but with microsecond set to 0. """
|
||||||
return dattim.replace(microsecond=0)
|
return dattim.replace(microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time_str(time_str):
|
||||||
|
""" Parse a time string (00:20:00) into Time object.
|
||||||
|
Return None if invalid.
|
||||||
|
"""
|
||||||
|
parts = str(time_str).split(':')
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
hour = int(parts[0])
|
||||||
|
minute = int(parts[1])
|
||||||
|
second = int(parts[2]) if len(parts) > 2 else 0
|
||||||
|
return dt.time(hour, minute, second)
|
||||||
|
except ValueError:
|
||||||
|
# ValueError if value cannot be converted to an int or not in range
|
||||||
|
return None
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Helpers to install PyPi packages."""
|
"""Helpers to install PyPi packages."""
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -15,25 +15,24 @@ def install_package(package, upgrade=True, target=None):
|
|||||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||||
Return boolean if install successfull."""
|
Return boolean if install successfull."""
|
||||||
# Not using 'import pip; pip.main([])' because it breaks the logger
|
# Not using 'import pip; pip.main([])' because it breaks the logger
|
||||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
|
||||||
|
|
||||||
if upgrade:
|
|
||||||
args.append('--upgrade')
|
|
||||||
if target:
|
|
||||||
args += ['--target', os.path.abspath(target)]
|
|
||||||
|
|
||||||
with INSTALL_LOCK:
|
with INSTALL_LOCK:
|
||||||
if check_package_exists(package, target):
|
if check_package_exists(package, target):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
_LOGGER.info('Attempting install of %s', package)
|
_LOGGER.info('Attempting install of %s', package)
|
||||||
|
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
||||||
|
if upgrade:
|
||||||
|
args.append('--upgrade')
|
||||||
|
if target:
|
||||||
|
args += ['--target', os.path.abspath(target)]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return 0 == subprocess.call(args)
|
return 0 == subprocess.call(args)
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_package_exists(package, target=None):
|
def check_package_exists(package, target):
|
||||||
"""Check if a package exists.
|
"""Check if a package exists.
|
||||||
Returns True when the requirement is met.
|
Returns True when the requirement is met.
|
||||||
Returns False when the package is not installed or doesn't meet req."""
|
Returns False when the package is not installed or doesn't meet req."""
|
||||||
@ -43,16 +42,5 @@ def check_package_exists(package, target=None):
|
|||||||
# This is a zip file
|
# This is a zip file
|
||||||
req = pkg_resources.Requirement.parse(urlparse(package).fragment)
|
req = pkg_resources.Requirement.parse(urlparse(package).fragment)
|
||||||
|
|
||||||
if target:
|
return any(dist in req for dist in
|
||||||
work_set = pkg_resources.WorkingSet([target])
|
pkg_resources.find_distributions(target))
|
||||||
search_fun = work_set.find
|
|
||||||
|
|
||||||
else:
|
|
||||||
search_fun = pkg_resources.get_distribution
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = search_fun(req)
|
|
||||||
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return bool(result)
|
|
||||||
|
9
script/bootstrap
Executable file
9
script/bootstrap
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/bootstrap: Resolve all dependencies that the application requires to
|
||||||
|
# run.
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
script/bootstrap_server
|
||||||
|
script/bootstrap_frontend
|
5
script/bootstrap_frontend
Executable file
5
script/bootstrap_frontend
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
echo "Bootstrapping frontend..."
|
||||||
|
cd homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||||
|
npm install
|
||||||
|
npm run setup_js_dev
|
||||||
|
cd ../../../../..
|
10
script/bootstrap_server
Executable file
10
script/bootstrap_server
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "Update the submodule to latest version..."
|
||||||
|
git submodule update
|
||||||
|
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
python3 -m pip install --upgrade -r requirements_all.txt
|
||||||
|
|
||||||
|
echo "Installing development dependencies.."
|
||||||
|
python3 -m pip install --upgrade flake8 pylint coveralls pytest pytest-cov
|
@ -1,12 +1,8 @@
|
|||||||
# Builds the frontend for production
|
# Builds the frontend for production
|
||||||
|
|
||||||
# If current pwd is scripts, go 1 up.
|
cd "$(dirname "$0")/.."
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd homeassistant/components/frontend/www_static/home-assistant-polymer
|
cd homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||||
npm install
|
|
||||||
npm run frontend_prod
|
npm run frontend_prod
|
||||||
|
|
||||||
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
@ -3,10 +3,7 @@
|
|||||||
# apt-get install cython3 libudev-dev python-sphinx python3-setuptools
|
# apt-get install cython3 libudev-dev python-sphinx python3-setuptools
|
||||||
# pip3 install cython
|
# pip3 install cython
|
||||||
|
|
||||||
# If current pwd is scripts, go 1 up.
|
cd "$(dirname "$0")/.."
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d build ]; then
|
if [ ! -d build ]; then
|
||||||
mkdir build
|
mkdir build
|
7
script/cibuild
Executable file
7
script/cibuild
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/cibuild: Setup environment for CI to run tests. This is primarily
|
||||||
|
# designed to run on the continuous integration server.
|
||||||
|
|
||||||
|
script/test coverage
|
||||||
|
coveralls
|
@ -3,10 +3,7 @@
|
|||||||
# Optional: pass in a timezone as first argument
|
# Optional: pass in a timezone as first argument
|
||||||
# If not given will attempt to mount /etc/localtime
|
# If not given will attempt to mount /etc/localtime
|
||||||
|
|
||||||
# If current pwd is scripts, go 1 up.
|
cd "$(dirname "$0")/.."
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker build -t home-assistant-dev .
|
docker build -t home-assistant-dev .
|
||||||
|
|
@ -1,10 +1,7 @@
|
|||||||
# Open a docker that can be used to debug/dev python-openzwave
|
# Open a docker that can be used to debug/dev python-openzwave
|
||||||
# Pass in a command line argument to build
|
# Pass in a command line argument to build
|
||||||
|
|
||||||
# If current pwd is scripts, go 1 up.
|
cd "$(dirname "$0")/.."
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $# -gt 0 ]
|
if [ $# -gt 0 ]
|
||||||
then
|
then
|
0
scripts/hass-daemon → script/hass-daemon
Normal file → Executable file
0
scripts/hass-daemon → script/hass-daemon
Normal file → Executable file
9
script/lint
Executable file
9
script/lint
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
# Run style checks
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "Checking style with flake8..."
|
||||||
|
flake8 homeassistant
|
||||||
|
|
||||||
|
echo "Checking style with pylint..."
|
||||||
|
pylint homeassistant
|
8
script/server
Executable file
8
script/server
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/server: Launch the application and any extra required processes
|
||||||
|
# locally.
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
python3 -m homeassistant -c config
|
5
script/setup
Executable file
5
script/setup
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
git submodule init
|
||||||
|
script/bootstrap
|
||||||
|
python3 setup.py develop
|
16
script/test
Executable file
16
script/test
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/test: Run test suite for application. Optionallly pass in a path to an
|
||||||
|
# individual test file to run a single test.
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
script/lint
|
||||||
|
|
||||||
|
echo "Running tests..."
|
||||||
|
|
||||||
|
if [ "$1" = "coverage" ]; then
|
||||||
|
py.test --cov homeassistant tests
|
||||||
|
else
|
||||||
|
py.test tests
|
||||||
|
fi
|
8
script/update
Executable file
8
script/update
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/update: Update application to run for its current checkout.
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
git pull
|
||||||
|
git submodule update
|
@ -1,9 +0,0 @@
|
|||||||
# Run style checks
|
|
||||||
|
|
||||||
# If current pwd is scripts, go 1 up.
|
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
flake8 homeassistant
|
|
||||||
pylint homeassistant
|
|
@ -1,10 +0,0 @@
|
|||||||
# If current pwd is scripts, go 1 up.
|
|
||||||
if [ ${PWD##*/} == "scripts" ]; then
|
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$1" = "coverage" ]; then
|
|
||||||
coverage run -m unittest discover tests
|
|
||||||
else
|
|
||||||
python3 -m unittest discover tests
|
|
||||||
fi
|
|
@ -1,6 +0,0 @@
|
|||||||
echo "The update script has been deprecated since Home Assistant v0.7"
|
|
||||||
echo
|
|
||||||
echo "Home Assistant is now distributed via PyPi and can be installed and"
|
|
||||||
echo "upgraded by running: pip3 install --upgrade homeassistant"
|
|
||||||
echo
|
|
||||||
echo "If you are developing a new feature for Home Assistant, run: git pull"
|
|
@ -1,15 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_event
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests demo component.
|
Tests event automation.
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.automation.event as event
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
|
||||||
|
|
||||||
|
|
||||||
class TestAutomationEvent(unittest.TestCase):
|
class TestAutomationEvent(unittest.TestCase):
|
||||||
@ -28,20 +26,57 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_fails_setup_if_no_event_type(self):
|
def test_old_config_if_fires_on_event(self):
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'event_type': 'test_event',
|
||||||
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_old_config_if_fires_on_event_with_data(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
'event_data': {'some_attr': 'some_value'},
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event', {'some_attr': 'some_value'})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_old_config_if_not_fires_if_event_data_not_matches(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
'event_data': {'some_attr': 'some_value'},
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_on_event(self):
|
def test_if_fires_on_event(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -52,10 +87,14 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
def test_if_fires_on_event_with_data(self):
|
def test_if_fires_on_event_with_data(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
event.CONF_EVENT_DATA: {'some_attr': 'some_value'},
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'event_data': {'some_attr': 'some_value'}
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -66,10 +105,14 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
def test_if_not_fires_if_event_data_not_matches(self):
|
def test_if_not_fires_if_event_data_not_matches(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
event.CONF_EVENT_DATA: {'some_attr': 'some_value'},
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'event_data': {'some_attr': 'some_value'}
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_init
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Tests automation component.
|
||||||
Tests demo component.
|
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.automation.event as event
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.const import CONF_PLATFORM, ATTR_ENTITY_ID
|
|
||||||
|
|
||||||
|
|
||||||
class TestAutomationEvent(unittest.TestCase):
|
class TestAutomationEvent(unittest.TestCase):
|
||||||
@ -28,20 +26,13 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_setup_fails_if_unknown_platform(self):
|
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'i_do_not_exist'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
def test_service_data_not_a_dict(self):
|
def test_service_data_not_a_dict(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'platform': 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'execute_service': 'test.automation',
|
||||||
automation.CONF_SERVICE_DATA: 100
|
'service_data': 100
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -49,13 +40,64 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_old_config_service_specify_data(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_data': {'some': 'data'}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual('data', self.calls[0].data['some'])
|
||||||
|
|
||||||
|
def test_old_config_service_specify_entity_id(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual(['hello.world'],
|
||||||
|
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
def test_old_config_service_specify_entity_id_list(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_entity_id': ['hello.world', 'hello.world2']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual(['hello.world', 'hello.world2'],
|
||||||
|
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
def test_service_specify_data(self):
|
def test_service_specify_data(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE_DATA: {'some': 'data'}
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_data': {'some': 'data'}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -67,29 +109,216 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
def test_service_specify_entity_id(self):
|
def test_service_specify_entity_id(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE_ENTITY_ID: 'hello.world'
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_entity_id': 'hello.world'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID])
|
self.assertEqual(['hello.world'],
|
||||||
|
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
def test_service_specify_entity_id_list(self):
|
def test_service_specify_entity_id_list(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE_ENTITY_ID: ['hello.world', 'hello.world2']
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
'service_entity_id': ['hello.world', 'hello.world2']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual(['hello.world', 'hello.world2'],
|
||||||
|
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
def test_two_triggers(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': [
|
||||||
|
{
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.hass.states.set('test.entity', 'hello')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
def test_two_conditions_with_and(self):
|
||||||
|
entity_id = 'test.entity'
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': [
|
||||||
|
{
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'condition': [
|
||||||
|
{
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'state': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'platform': 'numeric_state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'below': 150
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 100)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 101)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 151)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_two_conditions_with_or(self):
|
||||||
|
entity_id = 'test.entity'
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': [
|
||||||
|
{
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'condition_type': 'OR',
|
||||||
|
'condition': [
|
||||||
|
{
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'state': 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'platform': 'numeric_state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'below': 150
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 200)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 100)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 250)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
def test_using_trigger_as_condition(self):
|
||||||
|
""" """
|
||||||
|
entity_id = 'test.entity'
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': [
|
||||||
|
{
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'state': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'platform': 'numeric_state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'below': 150
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'condition': 'use_trigger_values',
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 100)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 120)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 151)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_using_trigger_as_condition_with_invalid_condition(self):
|
||||||
|
""" Event is not a valid condition. Will it still work? """
|
||||||
|
entity_id = 'test.entity'
|
||||||
|
self.hass.states.set(entity_id, 100)
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': [
|
||||||
|
{
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'platform': 'numeric_state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'below': 150
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'condition': 'use_trigger_values',
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data[ATTR_ENTITY_ID])
|
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_mqtt
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests demo component.
|
Tests mqtt automation.
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.automation.mqtt as mqtt
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
|
||||||
|
|
||||||
from tests.common import mock_mqtt_component, fire_mqtt_message
|
from tests.common import mock_mqtt_component, fire_mqtt_message
|
||||||
|
|
||||||
|
|
||||||
@ -31,20 +28,57 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_setup_fails_if_no_topic(self):
|
def test_old_config_if_fires_on_topic_match(self):
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'mqtt',
|
'platform': 'mqtt',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'mqtt_topic': 'test-topic',
|
||||||
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
fire_mqtt_message(self.hass, 'test-topic', '')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_old_config_if_fires_on_topic_and_payload_match(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'mqtt',
|
||||||
|
'mqtt_topic': 'test-topic',
|
||||||
|
'mqtt_payload': 'hello',
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_mqtt_message(self.hass, 'test-topic', 'hello')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_old_config_if_not_fires_on_topic_but_no_payload_match(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'platform': 'mqtt',
|
||||||
|
'mqtt_topic': 'test-topic',
|
||||||
|
'mqtt_payload': 'hello',
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_mqtt_message(self.hass, 'test-topic', 'no-hello')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_on_topic_match(self):
|
def test_if_fires_on_topic_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'mqtt',
|
'trigger': {
|
||||||
mqtt.CONF_TOPIC: 'test-topic',
|
'platform': 'mqtt',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'topic': 'test-topic'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -55,10 +89,14 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
def test_if_fires_on_topic_and_payload_match(self):
|
def test_if_fires_on_topic_and_payload_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'mqtt',
|
'trigger': {
|
||||||
mqtt.CONF_TOPIC: 'test-topic',
|
'platform': 'mqtt',
|
||||||
mqtt.CONF_PAYLOAD: 'hello',
|
'topic': 'test-topic',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'payload': 'hello'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -69,10 +107,14 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
def test_if_not_fires_on_topic_but_no_payload_match(self):
|
def test_if_not_fires_on_topic_but_no_payload_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'mqtt',
|
'trigger': {
|
||||||
mqtt.CONF_TOPIC: 'test-topic',
|
'platform': 'mqtt',
|
||||||
mqtt.CONF_PAYLOAD: 'hello',
|
'topic': 'test-topic',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'payload': 'hello'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_numeric_state
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests demo component.
|
Tests numeric state automation.
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.automation import event, numeric_state
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
|
||||||
|
|
||||||
|
|
||||||
class TestAutomationNumericState(unittest.TestCase):
|
class TestAutomationNumericState(unittest.TestCase):
|
||||||
@ -28,31 +26,17 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_setup_fails_if_no_entity_id(self):
|
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'numeric_state',
|
|
||||||
numeric_state.CONF_BELOW: 10,
|
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
def test_setup_fails_if_no_condition(self):
|
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'numeric_state',
|
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
def test_if_fires_on_entity_change_below(self):
|
def test_if_fires_on_entity_change_below(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'below': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
# 9 is below 10
|
# 9 is below 10
|
||||||
@ -66,10 +50,14 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'below': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -78,17 +66,20 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
|
||||||
def test_if_not_fires_on_entity_change_below_to_below(self):
|
def test_if_not_fires_on_entity_change_below_to_below(self):
|
||||||
self.hass.states.set('test.entity', 9)
|
self.hass.states.set('test.entity', 9)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'below': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -97,14 +88,17 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
|
||||||
def test_if_fires_on_entity_change_above(self):
|
def test_if_fires_on_entity_change_above(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
# 11 is above 10
|
# 11 is above 10
|
||||||
@ -119,10 +113,14 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -131,7 +129,6 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
|
||||||
def test_if_not_fires_on_entity_change_above_to_above(self):
|
def test_if_not_fires_on_entity_change_above_to_above(self):
|
||||||
# set initial state
|
# set initial state
|
||||||
self.hass.states.set('test.entity', 11)
|
self.hass.states.set('test.entity', 11)
|
||||||
@ -139,10 +136,14 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 10,
|
'entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 10,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -154,11 +155,15 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
def test_if_fires_on_entity_change_below_range(self):
|
def test_if_fires_on_entity_change_below_range(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 5,
|
'entity_id': 'test.entity',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'below': 10,
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 5,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
# 9 is below 10
|
# 9 is below 10
|
||||||
@ -169,11 +174,15 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
def test_if_fires_on_entity_change_below_above_range(self):
|
def test_if_fires_on_entity_change_below_above_range(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 5,
|
'entity_id': 'test.entity',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'below': 10,
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 5,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
# 4 is below 5
|
# 4 is below 5
|
||||||
@ -187,11 +196,15 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 5,
|
'entity_id': 'test.entity',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'below': 10,
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 5,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -206,11 +219,15 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 5,
|
'entity_id': 'test.entity',
|
||||||
numeric_state.CONF_BELOW: 10,
|
'below': 10,
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'above': 5,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -222,10 +239,13 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
def test_if_not_fires_if_entity_not_match(self):
|
def test_if_not_fires_if_entity_not_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'trigger': {
|
||||||
numeric_state.CONF_ENTITY_ID: 'test.another_entity',
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: 10,
|
'entity_id': 'test.another_entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -238,19 +258,23 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
test_state = 10
|
test_state = 10
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'trigger': {
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'platform': 'event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'event_type': 'test_event',
|
||||||
automation.CONF_IF: [{
|
},
|
||||||
CONF_PLATFORM: 'numeric_state',
|
'condition': {
|
||||||
numeric_state.CONF_ENTITY_ID: entity_id,
|
'platform': 'numeric_state',
|
||||||
numeric_state.CONF_ABOVE: test_state,
|
'entity_id': entity_id,
|
||||||
numeric_state.CONF_BELOW: test_state + 2,
|
'above': test_state,
|
||||||
}]
|
'below': test_state + 2
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.hass.states.set(entity_id, test_state )
|
self.hass.states.set(entity_id, test_state)
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_state
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests demo component.
|
Tests state automation.
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.automation import event, state
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
|
||||||
|
|
||||||
|
|
||||||
class TestAutomationState(unittest.TestCase):
|
class TestAutomationState(unittest.TestCase):
|
||||||
@ -29,20 +27,12 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_setup_fails_if_no_entity_id(self):
|
def test_old_config_if_fires_on_entity_change(self):
|
||||||
self.assertFalse(automation.setup(self.hass, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'state',
|
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
def test_if_fires_on_entity_change(self):
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -50,13 +40,13 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_on_entity_change_with_from_filter(self):
|
def test_old_config_if_fires_on_entity_change_with_from_filter(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
state.CONF_FROM: 'hello',
|
'state_from': 'hello',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -64,13 +54,13 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_on_entity_change_with_to_filter(self):
|
def test_old_config_if_fires_on_entity_change_with_to_filter(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
state.CONF_TO: 'world',
|
'state_to': 'world',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -78,14 +68,14 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_on_entity_change_with_both_filters(self):
|
def test_old_config_if_fires_on_entity_change_with_both_filters(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
state.CONF_FROM: 'hello',
|
'state_from': 'hello',
|
||||||
state.CONF_TO: 'world',
|
'state_to': 'world',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -93,14 +83,14 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_not_fires_if_to_filter_not_match(self):
|
def test_old_config_if_not_fires_if_to_filter_not_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
state.CONF_FROM: 'hello',
|
'state_from': 'hello',
|
||||||
state.CONF_TO: 'world',
|
'state_to': 'world',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -108,16 +98,16 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
def test_if_not_fires_if_from_filter_not_match(self):
|
def test_old_config_if_not_fires_if_from_filter_not_match(self):
|
||||||
self.hass.states.set('test.entity', 'bye')
|
self.hass.states.set('test.entity', 'bye')
|
||||||
|
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.entity',
|
'state_entity_id': 'test.entity',
|
||||||
state.CONF_FROM: 'hello',
|
'state_from': 'hello',
|
||||||
state.CONF_TO: 'world',
|
'state_to': 'world',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -125,12 +115,12 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
def test_if_not_fires_if_entity_not_match(self):
|
def test_old_config_if_not_fires_if_entity_not_match(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: 'test.another_entity',
|
'state_entity_id': 'test.another_entity',
|
||||||
automation.CONF_SERVICE: 'test.automation'
|
'execute_service': 'test.automation'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -138,18 +128,18 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
def test_if_action(self):
|
def test_old_config_if_action(self):
|
||||||
entity_id = 'domain.test_entity'
|
entity_id = 'domain.test_entity'
|
||||||
test_state = 'new_state'
|
test_state = 'new_state'
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
'platform': 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
'event_type': 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
'execute_service': 'test.automation',
|
||||||
automation.CONF_IF: [{
|
'if': [{
|
||||||
CONF_PLATFORM: 'state',
|
'platform': 'state',
|
||||||
state.CONF_ENTITY_ID: entity_id,
|
'entity_id': entity_id,
|
||||||
state.CONF_STATE: test_state,
|
'state': test_state,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -165,3 +155,182 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_entity_change(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_entity_change_with_from_filter(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'from': 'hello'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_entity_change_with_to_filter(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'to': 'world'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_entity_change_with_state_filter(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'state': 'world'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_entity_change_with_both_filters(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'from': 'hello',
|
||||||
|
'to': 'world'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_if_to_filter_not_match(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'from': 'hello',
|
||||||
|
'to': 'world'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'moon')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_if_from_filter_not_match(self):
|
||||||
|
self.hass.states.set('test.entity', 'bye')
|
||||||
|
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'from': 'hello',
|
||||||
|
'to': 'world'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_if_entity_not_match(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': 'test.anoter_entity',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action(self):
|
||||||
|
entity_id = 'domain.test_entity'
|
||||||
|
test_state = 'new_state'
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'condition': [{
|
||||||
|
'platform': 'state',
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'state': test_state
|
||||||
|
}],
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, test_state)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, test_state + 'something')
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
141
tests/components/automation/test_sun.py
Normal file
141
tests/components/automation/test_sun.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
"""
|
||||||
|
tests.components.automation.test_sun
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests sun automation.
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
from homeassistant.components import sun
|
||||||
|
import homeassistant.components.automation as automation
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from tests.common import fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
class TestAutomationSun(unittest.TestCase):
|
||||||
|
""" Test the sun automation. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.config.components.append('sun')
|
||||||
|
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def record_call(service):
|
||||||
|
self.calls.append(service)
|
||||||
|
|
||||||
|
self.hass.services.register('test', 'automation', record_call)
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_sunset_trigger(self):
|
||||||
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||||
|
sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015',
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
||||||
|
trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||||
|
return_value=now):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'sun',
|
||||||
|
'event': 'sunset',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, trigger_time)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_sunrise_trigger(self):
|
||||||
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||||
|
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
||||||
|
trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||||
|
return_value=now):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'sun',
|
||||||
|
'event': 'sunrise',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, trigger_time)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_sunset_trigger_with_offset(self):
|
||||||
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||||
|
sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015',
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
||||||
|
trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||||
|
return_value=now):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'sun',
|
||||||
|
'event': 'sunset',
|
||||||
|
'offset': '0:30:00'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, trigger_time)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_sunrise_trigger_with_offset(self):
|
||||||
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||||
|
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
||||||
|
trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||||
|
return_value=now):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'sun',
|
||||||
|
'event': 'sunrise',
|
||||||
|
'offset': '-0:30:00'
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, trigger_time)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_demo
|
tests.components.automation.test_time
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests demo component.
|
Tests time automation.
|
||||||
"""
|
"""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import unittest
|
import unittest
|
||||||
@ -33,7 +33,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_if_fires_when_hour_matches(self):
|
def test_old_config_if_fires_when_hour_matches(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
@ -48,7 +48,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_when_minute_matches(self):
|
def test_old_config_if_fires_when_minute_matches(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
@ -63,7 +63,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_when_second_matches(self):
|
def test_old_config_if_fires_when_second_matches(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
@ -78,7 +78,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_fires_when_all_matches(self):
|
def test_old_config_if_fires_when_all_matches(self):
|
||||||
self.assertTrue(automation.setup(self.hass, {
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
@ -96,13 +96,13 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_action_before(self):
|
def test_old_config_if_action_before(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
CONF_PLATFORM: 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
automation.CONF_IF: {
|
'if': {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
time.CONF_BEFORE: '10:00'
|
time.CONF_BEFORE: '10:00'
|
||||||
}
|
}
|
||||||
@ -126,13 +126,13 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_action_after(self):
|
def test_old_config_if_action_after(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
CONF_PLATFORM: 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
automation.CONF_IF: {
|
'if': {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
time.CONF_AFTER: '10:00'
|
time.CONF_AFTER: '10:00'
|
||||||
}
|
}
|
||||||
@ -156,13 +156,13 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_action_one_weekday(self):
|
def test_old_config_if_action_one_weekday(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
CONF_PLATFORM: 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
automation.CONF_IF: {
|
'if': {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
time.CONF_WEEKDAY: 'mon',
|
time.CONF_WEEKDAY: 'mon',
|
||||||
}
|
}
|
||||||
@ -187,13 +187,13 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
def test_if_action_list_weekday(self):
|
def test_old_config_if_action_list_weekday(self):
|
||||||
automation.setup(self.hass, {
|
automation.setup(self.hass, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
CONF_PLATFORM: 'event',
|
CONF_PLATFORM: 'event',
|
||||||
event.CONF_EVENT_TYPE: 'test_event',
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
automation.CONF_SERVICE: 'test.automation',
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
automation.CONF_IF: {
|
'if': {
|
||||||
CONF_PLATFORM: 'time',
|
CONF_PLATFORM: 'time',
|
||||||
time.CONF_WEEKDAY: ['mon', 'tue'],
|
time.CONF_WEEKDAY: ['mon', 'tue'],
|
||||||
}
|
}
|
||||||
@ -225,3 +225,267 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(2, len(self.calls))
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_when_hour_matches(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'hours': 0,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_when_minute_matches(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'minutes': 0,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_when_second_matches(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'seconds': 0,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(second=0))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_when_all_matches(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'hours': 1,
|
||||||
|
'minutes': 2,
|
||||||
|
'seconds': 3,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(
|
||||||
|
hour=1, minute=2, second=3))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_using_after(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'after': '5:00:00',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(
|
||||||
|
hour=5, minute=0, second=0))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
@patch('homeassistant.components.automation.time._LOGGER.error')
|
||||||
|
def test_if_not_fires_using_wrong_after(self, mock_error):
|
||||||
|
""" YAML translates time values to total seconds. This should break the
|
||||||
|
before rule. """
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'time',
|
||||||
|
'after': 3605,
|
||||||
|
# Total seconds. Hour = 3600 second
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
fire_time_changed(self.hass, dt_util.utcnow().replace(
|
||||||
|
hour=1, minute=0, second=5))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
self.assertEqual(2, mock_error.call_count)
|
||||||
|
|
||||||
|
def test_if_action_before(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event'
|
||||||
|
},
|
||||||
|
'condition': {
|
||||||
|
'platform': 'time',
|
||||||
|
'before': '10:00',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=before_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=after_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_after(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event'
|
||||||
|
},
|
||||||
|
'condition': {
|
||||||
|
'platform': 'time',
|
||||||
|
'after': '10:00',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=before_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=after_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_one_weekday(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event'
|
||||||
|
},
|
||||||
|
'condition': {
|
||||||
|
'platform': 'time',
|
||||||
|
'weekday': 'mon',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
days_past_monday = dt_util.now().weekday()
|
||||||
|
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||||
|
tuesday = monday + timedelta(days=1)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=monday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=tuesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_list_weekday(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event'
|
||||||
|
},
|
||||||
|
'condition': {
|
||||||
|
'platform': 'time',
|
||||||
|
'weekday': ['mon', 'tue'],
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'execute_service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
days_past_monday = dt_util.now().weekday()
|
||||||
|
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||||
|
tuesday = monday + timedelta(days=1)
|
||||||
|
wednesday = tuesday + timedelta(days=1)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=monday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=tuesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=wednesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
@ -7,7 +7,7 @@ Tests the device tracker compoments.
|
|||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
@ -127,7 +127,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
||||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
timedelta(seconds=0))[0]
|
timedelta(seconds=0))[0]
|
||||||
self.assertEqual('DEV1', config.dev_id)
|
self.assertEqual('dev1', config.dev_id)
|
||||||
self.assertEqual(True, config.track)
|
self.assertEqual(True, config.track)
|
||||||
|
|
||||||
def test_discovery(self):
|
def test_discovery(self):
|
||||||
@ -145,17 +145,25 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
scanner.reset()
|
scanner.reset()
|
||||||
scanner.come_home('DEV1')
|
scanner.come_home('DEV1')
|
||||||
|
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
register_time = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
scan_time = datetime(2015, 9, 15, 23, 1, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
|
||||||
|
return_value=register_time):
|
||||||
|
self.assertTrue(device_tracker.setup(self.hass, {
|
||||||
|
'device_tracker': {
|
||||||
|
'platform': 'test',
|
||||||
|
'consider_home': 59,
|
||||||
|
}}))
|
||||||
|
|
||||||
self.assertEqual(STATE_HOME,
|
self.assertEqual(STATE_HOME,
|
||||||
self.hass.states.get('device_tracker.dev1').state)
|
self.hass.states.get('device_tracker.dev1').state)
|
||||||
|
|
||||||
scanner.leave_home('DEV1')
|
scanner.leave_home('DEV1')
|
||||||
|
|
||||||
now = dt_util.utcnow().replace(second=0) + timedelta(hours=1)
|
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
|
||||||
|
return_value=scan_time):
|
||||||
with patch('homeassistant.util.dt.utcnow', return_value=now):
|
fire_time_changed(self.hass, scan_time)
|
||||||
fire_time_changed(self.hass, now)
|
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(STATE_NOT_HOME,
|
self.assertEqual(STATE_NOT_HOME,
|
||||||
|
@ -32,9 +32,9 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
def test_slugify(self):
|
def test_slugify(self):
|
||||||
""" Test slugify. """
|
""" Test slugify. """
|
||||||
self.assertEqual("Test", util.slugify("T-!@#$!#@$!$est"))
|
self.assertEqual("test", util.slugify("T-!@#$!#@$!$est"))
|
||||||
self.assertEqual("Test_More", util.slugify("Test More"))
|
self.assertEqual("test_more", util.slugify("Test More"))
|
||||||
self.assertEqual("Test_More", util.slugify("Test_(More)"))
|
self.assertEqual("test_more", util.slugify("Test_(More)"))
|
||||||
|
|
||||||
def test_split_entity_id(self):
|
def test_split_entity_id(self):
|
||||||
""" Test split_entity_id. """
|
""" Test split_entity_id. """
|
||||||
|
Loading…
x
Reference in New Issue
Block a user