mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
commit
46fd23c452
22
.coveragerc
22
.coveragerc
@ -12,31 +12,37 @@ omit =
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/wink.py
|
||||
homeassistant/components/*/wink.py
|
||||
|
||||
homeassistant/components/zwave.py
|
||||
homeassistant/components/*/zwave.py
|
||||
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
homeassistant/components/*/tellstick.py
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/browser.py
|
||||
homeassistant/components/camera/*
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/device_tracker/luci.py
|
||||
homeassistant/components/device_tracker/netgear.py
|
||||
homeassistant/components/device_tracker/nmap_tracker.py
|
||||
homeassistant/components/device_tracker/tomato.py
|
||||
homeassistant/components/device_tracker/tplink.py
|
||||
homeassistant/components/discovery.py
|
||||
homeassistant/components/downloader.py
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/light/hue.py
|
||||
homeassistant/components/light/limitlessled.py
|
||||
homeassistant/components/media_player/cast.py
|
||||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/mpd.py
|
||||
homeassistant/components/notify/file.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
@ -47,6 +53,7 @@ omit =
|
||||
homeassistant/components/notify/syslog.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/edimax.py
|
||||
homeassistant/components/sensor/efergy.py
|
||||
homeassistant/components/sensor/forecast.py
|
||||
homeassistant/components/sensor/mysensors.py
|
||||
@ -57,8 +64,9 @@ omit =
|
||||
homeassistant/components/sensor/temper.py
|
||||
homeassistant/components/sensor/time_date.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/edimax.py
|
||||
homeassistant/components/switch/command_switch.py
|
||||
homeassistant/components/switch/hikvisioncam.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/wemo.py
|
||||
homeassistant/components/thermostat/nest.py
|
||||
|
||||
|
@ -13,6 +13,7 @@ import threading
|
||||
import enum
|
||||
import re
|
||||
import functools as ft
|
||||
from collections import namedtuple
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||
@ -32,7 +33,7 @@ TIMER_INTERVAL = 1 # seconds
|
||||
SERVICE_CALL_LIMIT = 10 # seconds
|
||||
|
||||
# Define number of MINIMUM worker threads.
|
||||
# During bootstrap of HA (see bootstrap.from_config_dict()) worker threads
|
||||
# During bootstrap of HA (see bootstrap._setup_component()) worker threads
|
||||
# will be added for each component that polls devices.
|
||||
MIN_WORKER_THREAD = 2
|
||||
|
||||
@ -41,6 +42,9 @@ ENTITY_ID_PATTERN = re.compile(r"^(?P<domain>\w+)\.(?P<entity>\w+)$")
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Temporary to support deprecated methods
|
||||
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
|
||||
|
||||
|
||||
class HomeAssistant(object):
|
||||
""" Core class to route all communication to right components. """
|
||||
@ -52,40 +56,12 @@ class HomeAssistant(object):
|
||||
self.states = StateMachine(self.bus)
|
||||
self.config = Config()
|
||||
|
||||
@property
|
||||
def components(self):
|
||||
""" DEPRECATED 3/21/2015. Use hass.config.components """
|
||||
_LOGGER.warning(
|
||||
'hass.components is deprecated. Use hass.config.components')
|
||||
return self.config.components
|
||||
|
||||
@property
|
||||
def local_api(self):
|
||||
""" DEPRECATED 3/21/2015. Use hass.config.api """
|
||||
_LOGGER.warning(
|
||||
'hass.local_api is deprecated. Use hass.config.api')
|
||||
return self.config.api
|
||||
|
||||
@property
|
||||
def config_dir(self):
|
||||
""" DEPRECATED 3/18/2015. Use hass.config.config_dir """
|
||||
_LOGGER.warning(
|
||||
'hass.config_dir is deprecated. Use hass.config.config_dir')
|
||||
return self.config.config_dir
|
||||
|
||||
def get_config_path(self, path):
|
||||
""" DEPRECATED 3/18/2015. Use hass.config.path """
|
||||
_LOGGER.warning(
|
||||
'hass.get_config_path is deprecated. Use hass.config.path')
|
||||
return self.config.path(path)
|
||||
|
||||
def start(self):
|
||||
""" Start home assistant. """
|
||||
_LOGGER.info(
|
||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||
|
||||
Timer(self)
|
||||
|
||||
create_timer(self)
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
def block_till_stopped(self):
|
||||
@ -93,110 +69,21 @@ class HomeAssistant(object):
|
||||
will block until called. """
|
||||
request_shutdown = threading.Event()
|
||||
|
||||
self.services.register(DOMAIN, SERVICE_HOMEASSISTANT_STOP,
|
||||
lambda service: request_shutdown.set())
|
||||
def stop_homeassistant(service):
|
||||
""" Stops Home Assistant. """
|
||||
request_shutdown.set()
|
||||
|
||||
self.services.register(
|
||||
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
|
||||
|
||||
while not request_shutdown.isSet():
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
self.stop()
|
||||
|
||||
def track_point_in_time(self, action, point_in_time):
|
||||
"""
|
||||
Adds a listener that fires once after a spefic point in time.
|
||||
"""
|
||||
utc_point_in_time = date_util.as_utc(point_in_time)
|
||||
|
||||
@ft.wraps(action)
|
||||
def utc_converter(utc_now):
|
||||
""" Converts passed in UTC now to local now. """
|
||||
action(date_util.as_local(utc_now))
|
||||
|
||||
self.track_point_in_utc_time(utc_converter, utc_point_in_time)
|
||||
|
||||
def track_point_in_utc_time(self, action, point_in_time):
|
||||
"""
|
||||
Adds a listener that fires once after a specific point in UTC time.
|
||||
"""
|
||||
|
||||
@ft.wraps(action)
|
||||
def point_in_time_listener(event):
|
||||
""" Listens for matching time_changed events. """
|
||||
now = event.data[ATTR_NOW]
|
||||
|
||||
if now >= point_in_time and \
|
||||
not hasattr(point_in_time_listener, 'run'):
|
||||
|
||||
# Set variable so that we will never run twice.
|
||||
# Because the event bus might have to wait till a thread comes
|
||||
# available to execute this listener it might occur that the
|
||||
# listener gets lined up twice to be executed. This will make
|
||||
# sure the second time it does nothing.
|
||||
point_in_time_listener.run = True
|
||||
|
||||
self.bus.remove_listener(EVENT_TIME_CHANGED,
|
||||
point_in_time_listener)
|
||||
|
||||
action(now)
|
||||
|
||||
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
||||
return point_in_time_listener
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_utc_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None):
|
||||
""" Adds a listener that will fire if time matches a pattern. """
|
||||
self.track_time_change(
|
||||
action, year, month, day, hour, minute, second, utc=True)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None, utc=False):
|
||||
""" Adds a listener that will fire if UTC time matches a pattern. """
|
||||
|
||||
# We do not have to wrap the function with time pattern matching logic
|
||||
# if no pattern given
|
||||
if any((val is not None for val in
|
||||
(year, month, day, hour, minute, second))):
|
||||
|
||||
pmp = _process_match_param
|
||||
year, month, day = pmp(year), pmp(month), pmp(day)
|
||||
hour, minute, second = pmp(hour), pmp(minute), pmp(second)
|
||||
|
||||
@ft.wraps(action)
|
||||
def time_listener(event):
|
||||
""" Listens for matching time_changed events. """
|
||||
now = event.data[ATTR_NOW]
|
||||
|
||||
if not utc:
|
||||
now = date_util.as_local(now)
|
||||
|
||||
mat = _matcher
|
||||
|
||||
if mat(now.year, year) and \
|
||||
mat(now.month, month) and \
|
||||
mat(now.day, day) and \
|
||||
mat(now.hour, hour) and \
|
||||
mat(now.minute, minute) and \
|
||||
mat(now.second, second):
|
||||
|
||||
action(now)
|
||||
|
||||
else:
|
||||
@ft.wraps(action)
|
||||
def time_listener(event):
|
||||
""" Fires every time event that comes in. """
|
||||
action(event.data[ATTR_NOW])
|
||||
|
||||
self.bus.listen(EVENT_TIME_CHANGED, time_listener)
|
||||
return time_listener
|
||||
|
||||
def stop(self):
|
||||
""" Stops Home Assistant and shuts down all threads. """
|
||||
_LOGGER.info("Stopping")
|
||||
@ -208,76 +95,45 @@ class HomeAssistant(object):
|
||||
|
||||
self.pool.stop()
|
||||
|
||||
def get_entity_ids(self, domain_filter=None):
|
||||
"""
|
||||
Returns known entity ids.
|
||||
|
||||
THIS METHOD IS DEPRECATED. Use hass.states.entity_ids
|
||||
"""
|
||||
def track_point_in_time(self, action, point_in_time):
|
||||
"""Deprecated method as of 8/4/2015 to track point in time."""
|
||||
_LOGGER.warning(
|
||||
"hass.get_entiy_ids is deprecated. Use hass.states.entity_ids")
|
||||
'hass.track_point_in_time is deprecated. '
|
||||
'Please use homeassistant.helpers.event.track_point_in_time')
|
||||
import homeassistant.helpers.event as helper
|
||||
helper.track_point_in_time(self, action, point_in_time)
|
||||
|
||||
return self.states.entity_ids(domain_filter)
|
||||
|
||||
def listen_once_event(self, event_type, listener):
|
||||
""" Listen once for event of a specific type.
|
||||
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
|
||||
Note: at the moment it is impossible to remove a one time listener.
|
||||
|
||||
THIS METHOD IS DEPRECATED. Please use hass.events.listen_once.
|
||||
"""
|
||||
def track_point_in_utc_time(self, action, point_in_time):
|
||||
"""Deprecated method as of 8/4/2015 to track point in UTC time."""
|
||||
_LOGGER.warning(
|
||||
"hass.listen_once_event is deprecated. Use hass.bus.listen_once")
|
||||
'hass.track_point_in_utc_time is deprecated. '
|
||||
'Please use homeassistant.helpers.event.track_point_in_utc_time')
|
||||
import homeassistant.helpers.event as helper
|
||||
helper.track_point_in_utc_time(self, action, point_in_time)
|
||||
|
||||
self.bus.listen_once(event_type, listener)
|
||||
def track_utc_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None):
|
||||
"""Deprecated method as of 8/4/2015 to track UTC time change."""
|
||||
# pylint: disable=too-many-arguments
|
||||
_LOGGER.warning(
|
||||
'hass.track_utc_time_change is deprecated. '
|
||||
'Please use homeassistant.helpers.event.track_utc_time_change')
|
||||
import homeassistant.helpers.event as helper
|
||||
helper.track_utc_time_change(self, action, year, month, day, hour,
|
||||
minute, second)
|
||||
|
||||
def track_state_change(self, entity_ids, action,
|
||||
from_state=None, to_state=None):
|
||||
"""
|
||||
Track specific state changes.
|
||||
entity_ids, from_state and to_state can be string or list.
|
||||
Use list to match multiple.
|
||||
|
||||
THIS METHOD IS DEPRECATED. Use hass.states.track_change
|
||||
"""
|
||||
_LOGGER.warning((
|
||||
"hass.track_state_change is deprecated. "
|
||||
"Use hass.states.track_change"))
|
||||
|
||||
self.states.track_change(entity_ids, action, from_state, to_state)
|
||||
|
||||
def call_service(self, domain, service, service_data=None):
|
||||
"""
|
||||
Fires event to call specified service.
|
||||
|
||||
THIS METHOD IS DEPRECATED. Use hass.services.call
|
||||
"""
|
||||
_LOGGER.warning((
|
||||
"hass.services.call is deprecated. "
|
||||
"Use hass.services.call"))
|
||||
|
||||
self.services.call(domain, service, service_data)
|
||||
|
||||
|
||||
def _process_match_param(parameter):
|
||||
""" Wraps parameter in a list if it is not one and returns it. """
|
||||
if parameter is None or parameter == MATCH_ALL:
|
||||
return MATCH_ALL
|
||||
elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
|
||||
return (parameter,)
|
||||
else:
|
||||
return tuple(parameter)
|
||||
|
||||
|
||||
def _matcher(subject, pattern):
|
||||
""" Returns True if subject matches the pattern.
|
||||
|
||||
Pattern is either a list of allowed subjects or a `MATCH_ALL`.
|
||||
"""
|
||||
return MATCH_ALL == pattern or subject in pattern
|
||||
def track_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None, utc=False):
|
||||
"""Deprecated method as of 8/4/2015 to track time change."""
|
||||
# pylint: disable=too-many-arguments
|
||||
_LOGGER.warning(
|
||||
'hass.track_time_change is deprecated. '
|
||||
'Please use homeassistant.helpers.event.track_time_change')
|
||||
import homeassistant.helpers.event as helper
|
||||
helper.track_time_change(self, action, year, month, day, hour,
|
||||
minute, second)
|
||||
|
||||
|
||||
class JobPriority(util.OrderedEnum):
|
||||
@ -305,33 +161,6 @@ class JobPriority(util.OrderedEnum):
|
||||
return JobPriority.EVENT_DEFAULT
|
||||
|
||||
|
||||
def create_worker_pool():
|
||||
""" Creates a worker pool to be used. """
|
||||
|
||||
def job_handler(job):
|
||||
""" Called whenever a job is available to do. """
|
||||
try:
|
||||
func, arg = job
|
||||
func(arg)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Catch any exception our service/event_listener might throw
|
||||
# We do not want to crash our ThreadPool
|
||||
_LOGGER.exception("BusHandler:Exception doing job")
|
||||
|
||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
||||
""" Callback to be called when the pool queue gets too big. """
|
||||
|
||||
_LOGGER.warning(
|
||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
||||
worker_count, pending_jobs_count)
|
||||
|
||||
for start, job in current_jobs:
|
||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
||||
date_util.datetime_to_local_str(start), job)
|
||||
|
||||
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
||||
|
||||
|
||||
class EventOrigin(enum.Enum):
|
||||
""" Distinguish between origin of event. """
|
||||
# pylint: disable=no-init,too-few-public-methods
|
||||
@ -354,7 +183,7 @@ class Event(object):
|
||||
self.event_type = event_type
|
||||
self.data = data or {}
|
||||
self.origin = origin
|
||||
self.time_fired = util.strip_microseconds(
|
||||
self.time_fired = date_util.strip_microseconds(
|
||||
time_fired or date_util.utcnow())
|
||||
|
||||
def as_dict(self):
|
||||
@ -446,25 +275,28 @@ class EventBus(object):
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
|
||||
Note: at the moment it is impossible to remove a one time listener.
|
||||
Returns registered listener that can be used with remove_listener.
|
||||
"""
|
||||
@ft.wraps(listener)
|
||||
def onetime_listener(event):
|
||||
""" Removes listener from eventbus and then fires listener. """
|
||||
if not hasattr(onetime_listener, 'run'):
|
||||
# Set variable so that we will never run twice.
|
||||
# Because the event bus might have to wait till a thread comes
|
||||
# available to execute this listener it might occur that the
|
||||
# listener gets lined up twice to be executed.
|
||||
# This will make sure the second time it does nothing.
|
||||
onetime_listener.run = True
|
||||
if hasattr(onetime_listener, 'run'):
|
||||
return
|
||||
# Set variable so that we will never run twice.
|
||||
# Because the event bus might have to wait till a thread comes
|
||||
# available to execute this listener it might occur that the
|
||||
# listener gets lined up twice to be executed.
|
||||
# This will make sure the second time it does nothing.
|
||||
onetime_listener.run = True
|
||||
|
||||
self.remove_listener(event_type, onetime_listener)
|
||||
self.remove_listener(event_type, onetime_listener)
|
||||
|
||||
listener(event)
|
||||
listener(event)
|
||||
|
||||
self.listen(event_type, onetime_listener)
|
||||
|
||||
return onetime_listener
|
||||
|
||||
def remove_listener(self, event_type, listener):
|
||||
""" Removes a listener of a specific event_type. """
|
||||
with self._lock:
|
||||
@ -596,18 +428,19 @@ class StateMachine(object):
|
||||
|
||||
def entity_ids(self, domain_filter=None):
|
||||
""" List of entity ids that are being tracked. """
|
||||
if domain_filter is not None:
|
||||
domain_filter = domain_filter.lower()
|
||||
|
||||
return [state.entity_id for key, state
|
||||
in self._states.items()
|
||||
if util.split_entity_id(key)[0] == domain_filter]
|
||||
else:
|
||||
if domain_filter is None:
|
||||
return list(self._states.keys())
|
||||
|
||||
domain_filter = domain_filter.lower()
|
||||
|
||||
return [state.entity_id for key, state
|
||||
in self._states.items()
|
||||
if util.split_entity_id(key)[0] == domain_filter]
|
||||
|
||||
def all(self):
|
||||
""" Returns a list of all states. """
|
||||
return [state.copy() for state in self._states.values()]
|
||||
with self._lock:
|
||||
return [state.copy() for state in self._states.values()]
|
||||
|
||||
def get(self, entity_id):
|
||||
""" Returns the state of the specified entity. """
|
||||
@ -616,16 +449,6 @@ class StateMachine(object):
|
||||
# Make a copy so people won't mutate the state
|
||||
return state.copy() if state else None
|
||||
|
||||
def get_since(self, point_in_time):
|
||||
"""
|
||||
Returns all states that have been changed since point_in_time.
|
||||
"""
|
||||
point_in_time = date_util.strip_microseconds(point_in_time)
|
||||
|
||||
with self._lock:
|
||||
return [state for state in self._states.values()
|
||||
if state.last_updated >= point_in_time]
|
||||
|
||||
def is_state(self, entity_id, state):
|
||||
""" Returns True if entity exists and is specified state. """
|
||||
entity_id = entity_id.lower()
|
||||
@ -661,59 +484,32 @@ class StateMachine(object):
|
||||
same_state = is_existing and old_state.state == new_state
|
||||
same_attr = is_existing and old_state.attributes == attributes
|
||||
|
||||
if same_state and same_attr:
|
||||
return
|
||||
|
||||
# If state did not exist or is different, set it
|
||||
if not (same_state and same_attr):
|
||||
last_changed = old_state.last_changed if same_state else None
|
||||
last_changed = old_state.last_changed if same_state else None
|
||||
|
||||
state = State(entity_id, new_state, attributes, last_changed)
|
||||
self._states[entity_id] = state
|
||||
state = State(entity_id, new_state, attributes, last_changed)
|
||||
self._states[entity_id] = state
|
||||
|
||||
event_data = {'entity_id': entity_id, 'new_state': state}
|
||||
event_data = {'entity_id': entity_id, 'new_state': state}
|
||||
|
||||
if old_state:
|
||||
event_data['old_state'] = old_state
|
||||
if old_state:
|
||||
event_data['old_state'] = old_state
|
||||
|
||||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||
|
||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
||||
"""
|
||||
Track specific state changes.
|
||||
entity_ids, from_state and to_state can be string or list.
|
||||
Use list to match multiple.
|
||||
|
||||
Returns the listener that listens on the bus for EVENT_STATE_CHANGED.
|
||||
Pass the return value into hass.bus.remove_listener to remove it.
|
||||
DEPRECATED AS OF 8/4/2015
|
||||
"""
|
||||
from_state = _process_match_param(from_state)
|
||||
to_state = _process_match_param(to_state)
|
||||
|
||||
# Ensure it is a lowercase list with entity ids we want to match on
|
||||
if isinstance(entity_ids, str):
|
||||
entity_ids = (entity_ids.lower(),)
|
||||
else:
|
||||
entity_ids = tuple(entity_id.lower() for entity_id in entity_ids)
|
||||
|
||||
@ft.wraps(action)
|
||||
def state_listener(event):
|
||||
""" The listener that listens for specific state changes. """
|
||||
if event.data['entity_id'] not in entity_ids:
|
||||
return
|
||||
|
||||
if 'old_state' in event.data:
|
||||
old_state = event.data['old_state'].state
|
||||
else:
|
||||
old_state = None
|
||||
|
||||
if _matcher(old_state, from_state) and \
|
||||
_matcher(event.data['new_state'].state, to_state):
|
||||
|
||||
action(event.data['entity_id'],
|
||||
event.data.get('old_state'),
|
||||
event.data['new_state'])
|
||||
|
||||
self._bus.listen(EVENT_STATE_CHANGED, state_listener)
|
||||
|
||||
return state_listener
|
||||
_LOGGER.warning(
|
||||
'hass.states.track_change is deprecated. '
|
||||
'Use homeassistant.helpers.event.track_state_change instead.')
|
||||
import homeassistant.helpers.event as helper
|
||||
helper.track_state_change(_MockHA(self._bus), entity_ids, action,
|
||||
from_state, to_state)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
@ -802,23 +598,15 @@ class ServiceRegistry(object):
|
||||
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
||||
executed_event.set()
|
||||
|
||||
self._bus.remove_listener(
|
||||
EVENT_SERVICE_EXECUTED, service_executed)
|
||||
|
||||
self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed)
|
||||
|
||||
self._bus.fire(EVENT_CALL_SERVICE, event_data)
|
||||
|
||||
if blocking:
|
||||
# wait will return False if event not set after our limit has
|
||||
# passed. If not set, clean up the listener
|
||||
if not executed_event.wait(SERVICE_CALL_LIMIT):
|
||||
self._bus.remove_listener(
|
||||
EVENT_SERVICE_EXECUTED, service_executed)
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
success = executed_event.wait(SERVICE_CALL_LIMIT)
|
||||
self._bus.remove_listener(
|
||||
EVENT_SERVICE_EXECUTED, service_executed)
|
||||
return success
|
||||
|
||||
def _event_to_service_call(self, event):
|
||||
""" Calls a service from an event. """
|
||||
@ -826,15 +614,16 @@ class ServiceRegistry(object):
|
||||
domain = service_data.pop(ATTR_DOMAIN, None)
|
||||
service = service_data.pop(ATTR_SERVICE, None)
|
||||
|
||||
with self._lock:
|
||||
if domain in self._services and service in self._services[domain]:
|
||||
service_call = ServiceCall(domain, service, service_data)
|
||||
if not self.has_service(domain, service):
|
||||
return
|
||||
|
||||
# Add a job to the pool that calls _execute_service
|
||||
self._pool.add_job(JobPriority.EVENT_SERVICE,
|
||||
(self._execute_service,
|
||||
(self._services[domain][service],
|
||||
service_call)))
|
||||
service_handler = self._services[domain][service]
|
||||
service_call = ServiceCall(domain, service, service_data)
|
||||
|
||||
# Add a job to the pool that calls _execute_service
|
||||
self._pool.add_job(JobPriority.EVENT_SERVICE,
|
||||
(self._execute_service,
|
||||
(service_handler, service_call)))
|
||||
|
||||
def _execute_service(self, service_and_call):
|
||||
""" Executes a service and fires a SERVICE_EXECUTED event. """
|
||||
@ -843,9 +632,8 @@ class ServiceRegistry(object):
|
||||
service(call)
|
||||
|
||||
self._bus.fire(
|
||||
EVENT_SERVICE_EXECUTED, {
|
||||
ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]
|
||||
})
|
||||
EVENT_SERVICE_EXECUTED,
|
||||
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
|
||||
|
||||
def _generate_unique_id(self):
|
||||
""" Generates a unique service call id. """
|
||||
@ -853,70 +641,6 @@ class ServiceRegistry(object):
|
||||
return "{}-{}".format(id(self), self._cur_id)
|
||||
|
||||
|
||||
class Timer(threading.Thread):
|
||||
""" Timer will sent out an event every TIMER_INTERVAL seconds. """
|
||||
|
||||
def __init__(self, hass, interval=None):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.daemon = True
|
||||
self.hass = hass
|
||||
self.interval = interval or TIMER_INTERVAL
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
# We want to be able to fire every time a minute starts (seconds=0).
|
||||
# We want this so other modules can use that to make sure they fire
|
||||
# every minute.
|
||||
assert 60 % self.interval == 0, "60 % TIMER_INTERVAL should be 0!"
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
|
||||
lambda event: self.start())
|
||||
|
||||
def run(self):
|
||||
""" Start the timer. """
|
||||
|
||||
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
lambda event: self._stop_event.set())
|
||||
|
||||
_LOGGER.info("Timer:starting")
|
||||
|
||||
last_fired_on_second = -1
|
||||
|
||||
calc_now = date_util.utcnow
|
||||
interval = self.interval
|
||||
|
||||
while not self._stop_event.isSet():
|
||||
now = calc_now()
|
||||
|
||||
# First check checks if we are not on a second matching the
|
||||
# timer interval. Second check checks if we did not already fire
|
||||
# this interval.
|
||||
if now.second % interval or \
|
||||
now.second == last_fired_on_second:
|
||||
|
||||
# Sleep till it is the next time that we have to fire an event.
|
||||
# Aim for halfway through the second that fits TIMER_INTERVAL.
|
||||
# If TIMER_INTERVAL is 10 fire at .5, 10.5, 20.5, etc seconds.
|
||||
# This will yield the best results because time.sleep() is not
|
||||
# 100% accurate because of non-realtime OS's
|
||||
slp_seconds = interval - now.second % interval + \
|
||||
.5 - now.microsecond/1000000.0
|
||||
|
||||
time.sleep(slp_seconds)
|
||||
|
||||
now = calc_now()
|
||||
|
||||
last_fired_on_second = now.second
|
||||
|
||||
# Event might have been set while sleeping
|
||||
if not self._stop_event.isSet():
|
||||
try:
|
||||
self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
||||
except HomeAssistantError:
|
||||
# HA raises error if firing event after it has shut down
|
||||
break
|
||||
|
||||
|
||||
class Config(object):
|
||||
""" Configuration settings for Home Assistant. """
|
||||
|
||||
@ -943,8 +667,8 @@ class Config(object):
|
||||
|
||||
def temperature(self, value, unit):
|
||||
""" Converts temperature to user preferred unit if set. """
|
||||
if not (unit and self.temperature_unit and
|
||||
unit != self.temperature_unit):
|
||||
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
|
||||
self.temperature_unit and unit != self.temperature_unit):
|
||||
return value, unit
|
||||
|
||||
try:
|
||||
@ -986,3 +710,93 @@ class InvalidEntityFormatError(HomeAssistantError):
|
||||
class NoEntitySpecifiedError(HomeAssistantError):
|
||||
""" When no entity is specified. """
|
||||
pass
|
||||
|
||||
|
||||
def create_timer(hass, interval=TIMER_INTERVAL):
|
||||
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
|
||||
# We want to be able to fire every time a minute starts (seconds=0).
|
||||
# We want this so other modules can use that to make sure they fire
|
||||
# every minute.
|
||||
assert 60 % interval == 0, "60 % TIMER_INTERVAL should be 0!"
|
||||
|
||||
def timer():
|
||||
"""Send an EVENT_TIME_CHANGED on interval."""
|
||||
stop_event = threading.Event()
|
||||
|
||||
def stop_timer(event):
|
||||
"""Stop the timer."""
|
||||
stop_event.set()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer)
|
||||
|
||||
_LOGGER.info("Timer:starting")
|
||||
|
||||
last_fired_on_second = -1
|
||||
|
||||
calc_now = date_util.utcnow
|
||||
|
||||
while not stop_event.isSet():
|
||||
now = calc_now()
|
||||
|
||||
# First check checks if we are not on a second matching the
|
||||
# timer interval. Second check checks if we did not already fire
|
||||
# this interval.
|
||||
if now.second % interval or \
|
||||
now.second == last_fired_on_second:
|
||||
|
||||
# Sleep till it is the next time that we have to fire an event.
|
||||
# Aim for halfway through the second that fits TIMER_INTERVAL.
|
||||
# If TIMER_INTERVAL is 10 fire at .5, 10.5, 20.5, etc seconds.
|
||||
# This will yield the best results because time.sleep() is not
|
||||
# 100% accurate because of non-realtime OS's
|
||||
slp_seconds = interval - now.second % interval + \
|
||||
.5 - now.microsecond/1000000.0
|
||||
|
||||
time.sleep(slp_seconds)
|
||||
|
||||
now = calc_now()
|
||||
|
||||
last_fired_on_second = now.second
|
||||
|
||||
# Event might have been set while sleeping
|
||||
if not stop_event.isSet():
|
||||
try:
|
||||
hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
||||
except HomeAssistantError:
|
||||
# HA raises error if firing event after it has shut down
|
||||
break
|
||||
|
||||
def start_timer(event):
|
||||
"""Start the timer."""
|
||||
thread = threading.Thread(target=timer)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer)
|
||||
|
||||
|
||||
def create_worker_pool(worker_count=MIN_WORKER_THREAD):
|
||||
""" Creates a worker pool to be used. """
|
||||
|
||||
def job_handler(job):
|
||||
""" Called whenever a job is available to do. """
|
||||
try:
|
||||
func, arg = job
|
||||
func(arg)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Catch any exception our service/event_listener might throw
|
||||
# We do not want to crash our ThreadPool
|
||||
_LOGGER.exception("BusHandler:Exception doing job")
|
||||
|
||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
||||
""" Callback to be called when the pool queue gets too big. """
|
||||
|
||||
_LOGGER.warning(
|
||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
||||
worker_count, pending_jobs_count)
|
||||
|
||||
for start, job in current_jobs:
|
||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
||||
date_util.datetime_to_local_str(start), job)
|
||||
|
||||
return util.ThreadPool(job_handler, worker_count, busy_callback)
|
||||
|
@ -63,12 +63,15 @@ def setup_component(hass, domain, config=None):
|
||||
|
||||
def _handle_requirements(component, name):
|
||||
""" Installs requirements for component. """
|
||||
if hasattr(component, 'REQUIREMENTS'):
|
||||
for req in component.REQUIREMENTS:
|
||||
if not pkg_util.install_package(req):
|
||||
_LOGGER.error('Not initializing %s because could not install '
|
||||
'dependency %s', name, req)
|
||||
return False
|
||||
if not hasattr(component, 'REQUIREMENTS'):
|
||||
return True
|
||||
|
||||
for req in component.REQUIREMENTS:
|
||||
if not pkg_util.install_package(req):
|
||||
_LOGGER.error('Not initializing %s because could not install '
|
||||
'dependency %s', name, req)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@ -83,33 +86,30 @@ def _setup_component(hass, domain, config):
|
||||
_LOGGER.error(
|
||||
'Not initializing %s because not all dependencies loaded: %s',
|
||||
domain, ", ".join(missing_deps))
|
||||
|
||||
return False
|
||||
|
||||
if not _handle_requirements(component, domain):
|
||||
return False
|
||||
|
||||
try:
|
||||
if component.setup(hass, config):
|
||||
hass.config.components.append(component.DOMAIN)
|
||||
|
||||
# Assumption: if a component does not depend on groups
|
||||
# it communicates with devices
|
||||
if group.DOMAIN not in component.DEPENDENCIES:
|
||||
hass.pool.add_worker()
|
||||
|
||||
hass.bus.fire(
|
||||
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
if not component.setup(hass, config):
|
||||
_LOGGER.error('component %s failed to initialize', domain)
|
||||
|
||||
return False
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception('Error during setup of component %s', domain)
|
||||
return False
|
||||
|
||||
return False
|
||||
hass.config.components.append(component.DOMAIN)
|
||||
|
||||
# Assumption: if a component does not depend on groups
|
||||
# it communicates with devices
|
||||
if group.DOMAIN not in component.DEPENDENCIES:
|
||||
hass.pool.add_worker()
|
||||
|
||||
hass.bus.fire(
|
||||
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def prepare_setup_platform(hass, config, domain, platform_name):
|
||||
|
@ -6,6 +6,7 @@ Offers state listening automation rules.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.const import MATCH_ALL
|
||||
|
||||
|
||||
@ -30,7 +31,7 @@ def register(hass, config, action):
|
||||
""" Listens for state changes and calls action. """
|
||||
action()
|
||||
|
||||
hass.states.track_change(
|
||||
entity_id, state_automation_listener, from_state, to_state)
|
||||
track_state_change(
|
||||
hass, entity_id, state_automation_listener, from_state, to_state)
|
||||
|
||||
return True
|
||||
|
@ -5,6 +5,7 @@ homeassistant.components.automation.time
|
||||
Offers time listening automation rules.
|
||||
"""
|
||||
from homeassistant.util import convert
|
||||
from homeassistant.helpers.event import track_time_change
|
||||
|
||||
CONF_HOURS = "time_hours"
|
||||
CONF_MINUTES = "time_minutes"
|
||||
@ -21,8 +22,7 @@ def register(hass, config, action):
|
||||
""" Listens for time changes and calls action. """
|
||||
action()
|
||||
|
||||
hass.track_time_change(
|
||||
time_automation_listener,
|
||||
hour=hours, minute=minutes, second=seconds)
|
||||
track_time_change(hass, time_automation_listener,
|
||||
hour=hours, minute=minutes, second=seconds)
|
||||
|
||||
return True
|
||||
|
@ -8,6 +8,7 @@ the state of the sun and devices.
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.helpers.event import track_point_in_time, track_state_change
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
||||
from . import light, sun, device_tracker, group
|
||||
@ -91,14 +92,14 @@ def setup(hass, config):
|
||||
|
||||
if start_point:
|
||||
for index, light_id in enumerate(light_ids):
|
||||
hass.track_point_in_time(turn_on(light_id),
|
||||
(start_point +
|
||||
index * LIGHT_TRANSITION_TIME))
|
||||
track_point_in_time(
|
||||
hass, turn_on(light_id),
|
||||
(start_point + index * LIGHT_TRANSITION_TIME))
|
||||
|
||||
# Track every time sun rises so we can schedule a time-based
|
||||
# pre-sun set event
|
||||
hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise,
|
||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
|
||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||
|
||||
# If the sun is already above horizon
|
||||
# schedule the time-based pre-sun set event
|
||||
@ -157,13 +158,13 @@ def setup(hass, config):
|
||||
light.turn_off(hass, light_ids)
|
||||
|
||||
# Track home coming of each device
|
||||
hass.states.track_change(
|
||||
device_entity_ids, check_light_on_dev_state_change,
|
||||
track_state_change(
|
||||
hass, device_entity_ids, check_light_on_dev_state_change,
|
||||
STATE_NOT_HOME, STATE_HOME)
|
||||
|
||||
# Track when all devices are gone to shut down lights
|
||||
hass.states.track_change(
|
||||
device_group, check_light_on_dev_state_change,
|
||||
track_state_change(
|
||||
hass, device_group, check_light_on_dev_state_change,
|
||||
STATE_HOME, STATE_NOT_HOME)
|
||||
|
||||
return True
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.helpers import validate_config
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
from homeassistant.const import (
|
||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
||||
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
|
||||
@ -134,7 +135,7 @@ class DeviceTracker(object):
|
||||
seconds = range(0, 60, seconds)
|
||||
|
||||
_LOGGER.info("Device tracker interval second=%s", seconds)
|
||||
hass.track_utc_time_change(update_device_state, second=seconds)
|
||||
track_utc_time_change(hass, update_device_state, second=seconds)
|
||||
|
||||
hass.services.register(DOMAIN,
|
||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||
|
@ -7,6 +7,7 @@ Provides functionality to group devices that can be turned on or off.
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.helpers import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import Entity
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import (
|
||||
@ -102,10 +103,6 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
||||
def setup(hass, config):
|
||||
""" Sets up all groups found definded in the configuration. """
|
||||
for name, entity_ids in config.get(DOMAIN, {}).items():
|
||||
# Support old deprecated method - 2/28/2015
|
||||
if isinstance(entity_ids, str):
|
||||
entity_ids = entity_ids.split(",")
|
||||
|
||||
setup_group(hass, name, entity_ids)
|
||||
|
||||
return True
|
||||
@ -162,8 +159,8 @@ class Group(Entity):
|
||||
|
||||
def start(self):
|
||||
""" Starts the tracking. """
|
||||
self.hass.states.track_change(
|
||||
self.tracking, self._state_changed_listener)
|
||||
track_state_change(
|
||||
self.hass, self.tracking, self._state_changed_listener)
|
||||
|
||||
def stop(self):
|
||||
""" Unregisters the group from Home Assistant. """
|
||||
|
@ -119,7 +119,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def setup(hass, config=None):
|
||||
""" Sets up the HTTP API and debug interface. """
|
||||
|
||||
if config is None or DOMAIN not in config:
|
||||
config = {DOMAIN: {}}
|
||||
|
||||
@ -139,9 +138,14 @@ def setup(hass, config=None):
|
||||
|
||||
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
|
||||
|
||||
server = HomeAssistantHTTPServer(
|
||||
(server_host, server_port), RequestHandler, hass, api_password,
|
||||
development, no_password_set, sessions_enabled)
|
||||
try:
|
||||
server = HomeAssistantHTTPServer(
|
||||
(server_host, server_port), RequestHandler, hass, api_password,
|
||||
development, no_password_set, sessions_enabled)
|
||||
except OSError:
|
||||
# Happens if address already in use
|
||||
_LOGGER.exception("Error setting up HTTP server")
|
||||
return False
|
||||
|
||||
hass.bus.listen_once(
|
||||
ha.EVENT_HOMEASSISTANT_START,
|
||||
@ -183,10 +187,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
_LOGGER.info("running http in development mode")
|
||||
|
||||
def start(self):
|
||||
""" Starts the server. """
|
||||
self.hass.bus.listen_once(
|
||||
ha.EVENT_HOMEASSISTANT_STOP,
|
||||
lambda event: self.shutdown())
|
||||
""" Starts the HTTP server. """
|
||||
def stop_http(event):
|
||||
""" Stops the HTTP server. """
|
||||
self.shutdown()
|
||||
|
||||
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
|
||||
|
||||
_LOGGER.info(
|
||||
"Starting web interface at http://%s:%d", *self.server_address)
|
||||
@ -199,7 +205,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
self.serve_forever()
|
||||
|
||||
def register_path(self, method, url, callback, require_auth=True):
|
||||
""" Regitsters a path wit the server. """
|
||||
""" Registers a path wit the server. """
|
||||
self.paths.append((method, url, callback, require_auth))
|
||||
|
||||
|
||||
|
@ -166,26 +166,25 @@ def setup(hass, config):
|
||||
profiles = {}
|
||||
|
||||
for profile_path in profile_paths:
|
||||
if not os.path.isfile(profile_path):
|
||||
continue
|
||||
with open(profile_path) as inp:
|
||||
reader = csv.reader(inp)
|
||||
|
||||
if os.path.isfile(profile_path):
|
||||
with open(profile_path) as inp:
|
||||
reader = csv.reader(inp)
|
||||
# Skip the header
|
||||
next(reader, None)
|
||||
|
||||
# Skip the header
|
||||
next(reader, None)
|
||||
try:
|
||||
for profile_id, color_x, color_y, brightness in reader:
|
||||
profiles[profile_id] = (float(color_x), float(color_y),
|
||||
int(brightness))
|
||||
except ValueError:
|
||||
# ValueError if not 4 values per row
|
||||
# ValueError if convert to float/int failed
|
||||
_LOGGER.error(
|
||||
"Error parsing light profiles from %s", profile_path)
|
||||
|
||||
try:
|
||||
for profile_id, color_x, color_y, brightness in reader:
|
||||
profiles[profile_id] = (float(color_x), float(color_y),
|
||||
int(brightness))
|
||||
|
||||
except ValueError:
|
||||
# ValueError if not 4 values per row
|
||||
# ValueError if convert to float/int failed
|
||||
_LOGGER.error(
|
||||
"Error parsing light profiles from %s", profile_path)
|
||||
|
||||
return False
|
||||
return False
|
||||
|
||||
def handle_light_service(service):
|
||||
""" Hande a turn light on or off service call. """
|
||||
@ -206,66 +205,70 @@ def setup(hass, config):
|
||||
for light in target_lights:
|
||||
light.turn_off(**params)
|
||||
|
||||
else:
|
||||
# Processing extra data for turn light on request
|
||||
|
||||
# We process the profile first so that we get the desired
|
||||
# behavior that extra service data attributes overwrite
|
||||
# profile values
|
||||
profile = profiles.get(dat.get(ATTR_PROFILE))
|
||||
|
||||
if profile:
|
||||
*params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile
|
||||
|
||||
if ATTR_BRIGHTNESS in dat:
|
||||
# We pass in the old value as the default parameter if parsing
|
||||
# of the new one goes wrong.
|
||||
params[ATTR_BRIGHTNESS] = util.convert(
|
||||
dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS))
|
||||
|
||||
if ATTR_XY_COLOR in dat:
|
||||
try:
|
||||
# xy_color should be a list containing 2 floats
|
||||
xycolor = dat.get(ATTR_XY_COLOR)
|
||||
|
||||
# Without this check, a xycolor with value '99' would work
|
||||
if not isinstance(xycolor, str):
|
||||
params[ATTR_XY_COLOR] = [float(val) for val in xycolor]
|
||||
|
||||
except (TypeError, ValueError):
|
||||
# TypeError if xy_color is not iterable
|
||||
# ValueError if value could not be converted to float
|
||||
pass
|
||||
|
||||
if ATTR_RGB_COLOR in dat:
|
||||
try:
|
||||
# rgb_color should be a list containing 3 ints
|
||||
rgb_color = dat.get(ATTR_RGB_COLOR)
|
||||
|
||||
if len(rgb_color) == 3:
|
||||
params[ATTR_XY_COLOR] = \
|
||||
color_util.color_RGB_to_xy(int(rgb_color[0]),
|
||||
int(rgb_color[1]),
|
||||
int(rgb_color[2]))
|
||||
|
||||
except (TypeError, ValueError):
|
||||
# TypeError if rgb_color is not iterable
|
||||
# ValueError if not all values can be converted to int
|
||||
pass
|
||||
|
||||
if ATTR_FLASH in dat:
|
||||
if dat[ATTR_FLASH] == FLASH_SHORT:
|
||||
params[ATTR_FLASH] = FLASH_SHORT
|
||||
|
||||
elif dat[ATTR_FLASH] == FLASH_LONG:
|
||||
params[ATTR_FLASH] = FLASH_LONG
|
||||
|
||||
if ATTR_EFFECT in dat:
|
||||
if dat[ATTR_EFFECT] == EFFECT_COLORLOOP:
|
||||
params[ATTR_EFFECT] = EFFECT_COLORLOOP
|
||||
|
||||
for light in target_lights:
|
||||
light.turn_on(**params)
|
||||
if light.should_poll:
|
||||
light.update_ha_state(True)
|
||||
return
|
||||
|
||||
# Processing extra data for turn light on request
|
||||
|
||||
# We process the profile first so that we get the desired
|
||||
# behavior that extra service data attributes overwrite
|
||||
# profile values
|
||||
profile = profiles.get(dat.get(ATTR_PROFILE))
|
||||
|
||||
if profile:
|
||||
*params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile
|
||||
|
||||
if ATTR_BRIGHTNESS in dat:
|
||||
# We pass in the old value as the default parameter if parsing
|
||||
# of the new one goes wrong.
|
||||
params[ATTR_BRIGHTNESS] = util.convert(
|
||||
dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS))
|
||||
|
||||
if ATTR_XY_COLOR in dat:
|
||||
try:
|
||||
# xy_color should be a list containing 2 floats
|
||||
xycolor = dat.get(ATTR_XY_COLOR)
|
||||
|
||||
# Without this check, a xycolor with value '99' would work
|
||||
if not isinstance(xycolor, str):
|
||||
params[ATTR_XY_COLOR] = [float(val) for val in xycolor]
|
||||
|
||||
except (TypeError, ValueError):
|
||||
# TypeError if xy_color is not iterable
|
||||
# ValueError if value could not be converted to float
|
||||
pass
|
||||
|
||||
if ATTR_RGB_COLOR in dat:
|
||||
try:
|
||||
# rgb_color should be a list containing 3 ints
|
||||
rgb_color = dat.get(ATTR_RGB_COLOR)
|
||||
|
||||
if len(rgb_color) == 3:
|
||||
params[ATTR_XY_COLOR] = \
|
||||
color_util.color_RGB_to_xy(int(rgb_color[0]),
|
||||
int(rgb_color[1]),
|
||||
int(rgb_color[2]))
|
||||
|
||||
except (TypeError, ValueError):
|
||||
# TypeError if rgb_color is not iterable
|
||||
# ValueError if not all values can be converted to int
|
||||
pass
|
||||
|
||||
if ATTR_FLASH in dat:
|
||||
if dat[ATTR_FLASH] == FLASH_SHORT:
|
||||
params[ATTR_FLASH] = FLASH_SHORT
|
||||
|
||||
elif dat[ATTR_FLASH] == FLASH_LONG:
|
||||
params[ATTR_FLASH] = FLASH_LONG
|
||||
|
||||
if ATTR_EFFECT in dat:
|
||||
if dat[ATTR_EFFECT] == EFFECT_COLORLOOP:
|
||||
params[ATTR_EFFECT] = EFFECT_COLORLOOP
|
||||
|
||||
for light in target_lights:
|
||||
light.turn_on(**params)
|
||||
|
||||
for light in target_lights:
|
||||
if light.should_poll:
|
||||
|
@ -98,9 +98,7 @@ ATTR_TO_PROPERTY = [
|
||||
def is_on(hass, entity_id=None):
|
||||
""" Returns true if specified media player entity_id is on.
|
||||
Will check all media player if no entity_id specified. """
|
||||
|
||||
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
|
||||
|
||||
return any(not hass.states.is_state(entity_id, STATE_OFF)
|
||||
for entity_id in entity_ids)
|
||||
|
||||
@ -108,28 +106,24 @@ def is_on(hass, entity_id=None):
|
||||
def turn_on(hass, entity_id=None):
|
||||
""" Will turn on specified media player or all. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
def turn_off(hass, entity_id=None):
|
||||
""" Will turn off specified media player or all. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
def volume_up(hass, entity_id=None):
|
||||
""" Send the media player the command for volume up. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
|
||||
|
||||
|
||||
def volume_down(hass, entity_id=None):
|
||||
""" Send the media player the command for volume down. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
|
||||
|
||||
|
||||
@ -156,35 +150,30 @@ def set_volume_level(hass, volume, entity_id=None):
|
||||
def media_play_pause(hass, entity_id=None):
|
||||
""" Send the media player the command for play/pause. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
|
||||
|
||||
|
||||
def media_play(hass, entity_id=None):
|
||||
""" Send the media player the command for play/pause. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
|
||||
|
||||
|
||||
def media_pause(hass, entity_id=None):
|
||||
""" Send the media player the command for play/pause. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
|
||||
|
||||
|
||||
def media_next_track(hass, entity_id=None):
|
||||
""" Send the media player the command for next track. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
|
||||
|
||||
|
||||
def media_previous_track(hass, entity_id=None):
|
||||
""" Send the media player the command for prev track. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
|
||||
|
||||
|
||||
@ -262,29 +251,30 @@ def setup(hass, config):
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service)
|
||||
|
||||
def play_youtube_video_service(service, media_id):
|
||||
def play_youtube_video_service(service, media_id=None):
|
||||
""" Plays specified media_id on the media player. """
|
||||
target_players = component.extract_from_service(service)
|
||||
if media_id is None:
|
||||
service.data.get('video')
|
||||
|
||||
if media_id:
|
||||
for player in target_players:
|
||||
player.play_youtube(media_id)
|
||||
if media_id is None:
|
||||
return
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
for player in component.extract_from_service(service):
|
||||
player.play_youtube(media_id)
|
||||
|
||||
hass.services.register(DOMAIN, "start_fireplace",
|
||||
lambda service:
|
||||
play_youtube_video_service(service, "eyU3bRy2x44"))
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, "start_epic_sax",
|
||||
lambda service:
|
||||
play_youtube_video_service(service, "kxopViU98Xo"))
|
||||
hass.services.register(
|
||||
DOMAIN, "start_fireplace",
|
||||
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO,
|
||||
lambda service:
|
||||
play_youtube_video_service(
|
||||
service, service.data.get('video')))
|
||||
hass.services.register(
|
||||
DOMAIN, "start_epic_sax",
|
||||
lambda service: play_youtube_video_service(service, "kxopViU98Xo"))
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1,52 +0,0 @@
|
||||
"""
|
||||
homeassistant.components.process
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides functionality to watch for specific processes running
|
||||
on the host machine.
|
||||
|
||||
Author: Markus Stenberg <fingon@iki.fi>
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
import homeassistant.util as util
|
||||
|
||||
DOMAIN = 'process'
|
||||
DEPENDENCIES = []
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
PS_STRING = 'ps awx'
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Sets up a check if specified processes are running.
|
||||
|
||||
processes: dict mapping entity id to substring to search for
|
||||
in process list.
|
||||
"""
|
||||
|
||||
# Deprecated as of 3/7/2015
|
||||
logging.getLogger(__name__).warning(
|
||||
"This component has been deprecated and will be removed in the future."
|
||||
" Please use sensor.systemmonitor with the process type")
|
||||
|
||||
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
|
||||
for pname, pstring in config[DOMAIN].items()}
|
||||
|
||||
def update_process_states(time):
|
||||
""" Check ps for currently running processes and update states. """
|
||||
with os.popen(PS_STRING, 'r') as psfile:
|
||||
lines = list(psfile)
|
||||
|
||||
for entity_id, pstring in entities.items():
|
||||
state = STATE_ON if any(pstring in l for l in lines) else STATE_OFF
|
||||
|
||||
hass.states.set(entity_id, state)
|
||||
|
||||
update_process_states(None)
|
||||
|
||||
hass.track_time_change(update_process_states, second=[0, 30])
|
||||
|
||||
return True
|
@ -19,6 +19,7 @@ import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from homeassistant import State
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.state import reproduce_state
|
||||
@ -104,8 +105,8 @@ class Scene(ToggleEntity):
|
||||
self.prev_states = None
|
||||
self.ignore_updates = False
|
||||
|
||||
self.hass.states.track_change(
|
||||
self.entity_ids, self.entity_state_changed)
|
||||
track_state_change(
|
||||
self.hass, self.entity_ids, self.entity_state_changed)
|
||||
|
||||
self.update()
|
||||
|
||||
|
@ -17,6 +17,7 @@ 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__)
|
||||
@ -62,7 +63,7 @@ class TimeEventListener(ServiceEventListener):
|
||||
""" Call the execute method """
|
||||
self.execute(hass)
|
||||
|
||||
hass.track_point_in_time(execute, next_time)
|
||||
track_point_in_time(hass, execute, next_time)
|
||||
|
||||
_LOGGER.info(
|
||||
'TimeEventListener scheduled for %s, will call service %s.%s',
|
||||
|
@ -10,6 +10,7 @@ from datetime import timedelta
|
||||
import homeassistant.util.dt as date_util
|
||||
import threading
|
||||
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
from homeassistant.util import split_entity_id
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED)
|
||||
@ -111,8 +112,8 @@ class Script(object):
|
||||
elif CONF_DELAY in action:
|
||||
delay = timedelta(**action[CONF_DELAY])
|
||||
point_in_time = date_util.now() + delay
|
||||
self.listener = self.hass.track_point_in_time(
|
||||
self, point_in_time)
|
||||
self.listener = track_point_in_time(
|
||||
self.hass, self, point_in_time)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -9,6 +9,7 @@ Provides a simple alarm feature:
|
||||
import logging
|
||||
|
||||
import homeassistant.loader as loader
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
|
||||
|
||||
DOMAIN = "simple_alarm"
|
||||
@ -83,8 +84,8 @@ def setup(hass, config):
|
||||
if not device_tracker.is_on(hass):
|
||||
unknown_alarm()
|
||||
|
||||
hass.states.track_change(
|
||||
light.ENTITY_ID_ALL_LIGHTS,
|
||||
track_state_change(
|
||||
hass, light.ENTITY_ID_ALL_LIGHTS,
|
||||
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
|
||||
|
||||
def ring_known_alarm(entity_id, old_state, new_state):
|
||||
@ -93,8 +94,8 @@ def setup(hass, config):
|
||||
known_alarm()
|
||||
|
||||
# Track home coming of each device
|
||||
hass.states.track_change(
|
||||
hass.states.entity_ids(device_tracker.DOMAIN),
|
||||
track_state_change(
|
||||
hass, hass.states.entity_ids(device_tracker.DOMAIN),
|
||||
ring_known_alarm, STATE_NOT_HOME, STATE_HOME)
|
||||
|
||||
return True
|
||||
|
@ -21,9 +21,12 @@ The sun event need to have the type 'sun', which service to call, which event
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import urllib
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.event import (
|
||||
track_point_in_utc_time, track_point_in_time)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components.scheduler import ServiceEventListener
|
||||
|
||||
@ -129,8 +132,13 @@ def setup(hass, config):
|
||||
|
||||
if elevation is None:
|
||||
google = GoogleGeocoder()
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation)
|
||||
try:
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info(
|
||||
'Retrieved elevation from Google: %s', location.elevation)
|
||||
except urllib.error.URLError:
|
||||
# If no internet connection available etc.
|
||||
pass
|
||||
|
||||
sun = Sun(hass, location)
|
||||
sun.point_in_time_listener(dt_util.utcnow())
|
||||
@ -203,8 +211,8 @@ class Sun(Entity):
|
||||
self.update_ha_state()
|
||||
|
||||
# Schedule next update at next_change+1 second so sun state has changed
|
||||
self.hass.track_point_in_utc_time(
|
||||
self.point_in_time_listener,
|
||||
track_point_in_utc_time(
|
||||
self.hass, self.point_in_time_listener,
|
||||
self.next_change + timedelta(seconds=1))
|
||||
|
||||
|
||||
@ -266,7 +274,7 @@ class SunEventListener(ServiceEventListener):
|
||||
""" Call the execute method. """
|
||||
self.execute(hass)
|
||||
|
||||
hass.track_point_in_time(execute, next_time)
|
||||
track_point_in_time(hass, execute, next_time)
|
||||
|
||||
return next_time
|
||||
|
||||
|
@ -45,21 +45,18 @@ _LOGGER = logging.getLogger(__name__)
|
||||
def is_on(hass, entity_id=None):
|
||||
""" Returns if the switch is on based on the statemachine. """
|
||||
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
|
||||
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
def turn_on(hass, entity_id=None):
|
||||
""" Turns all or specified switch on. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
def turn_off(hass, entity_id=None):
|
||||
""" Turns all or specified switch off. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
@ -84,7 +81,6 @@ def setup(hass, config):
|
||||
switch.update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)
|
||||
|
||||
return True
|
||||
|
@ -64,6 +64,7 @@ import homeassistant.components as core
|
||||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.components.thermostat import ThermostatDevice
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
|
||||
|
||||
TOL_TEMP = 0.3
|
||||
@ -108,12 +109,12 @@ class HeatControl(ThermostatDevice):
|
||||
self._away = False
|
||||
self._heater_manual_changed = True
|
||||
|
||||
hass.states.track_change(self.heater_entity_id,
|
||||
self._heater_turned_on,
|
||||
STATE_OFF, STATE_ON)
|
||||
hass.states.track_change(self.heater_entity_id,
|
||||
self._heater_turned_off,
|
||||
STATE_ON, STATE_OFF)
|
||||
track_state_change(hass, self.heater_entity_id,
|
||||
self._heater_turned_on,
|
||||
STATE_OFF, STATE_ON)
|
||||
track_state_change(hass, self.heater_entity_id,
|
||||
self._heater_turned_off,
|
||||
STATE_ON, STATE_OFF)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -13,14 +13,11 @@ from homeassistant.const import (
|
||||
CONF_TIME_ZONE)
|
||||
import homeassistant.util.location as loc_util
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||
CONF_CONFIG_FILE = 'home-assistant.conf'
|
||||
|
||||
DEFAULT_CONFIG = [
|
||||
DEFAULT_CONFIG = (
|
||||
# Tuples (attribute, default, auto detect property, description)
|
||||
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
|
||||
'running'),
|
||||
@ -30,9 +27,9 @@ DEFAULT_CONFIG = [
|
||||
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celcius, F for Fahrenheit'),
|
||||
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
||||
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
||||
]
|
||||
DEFAULT_COMPONENTS = [
|
||||
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun']
|
||||
)
|
||||
DEFAULT_COMPONENTS = (
|
||||
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun')
|
||||
|
||||
|
||||
def ensure_config_exists(config_dir, detect_location=True):
|
||||
@ -95,24 +92,14 @@ def create_default_config(config_dir, detect_location=True):
|
||||
|
||||
def find_config_file(config_dir):
|
||||
""" Looks in given directory for supported config files. """
|
||||
for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE):
|
||||
config_path = os.path.join(config_dir, filename)
|
||||
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
||||
|
||||
if os.path.isfile(config_path):
|
||||
return config_path
|
||||
|
||||
return None
|
||||
return config_path if os.path.isfile(config_path) else None
|
||||
|
||||
|
||||
def load_config_file(config_path):
|
||||
""" Loads given config file. """
|
||||
config_ext = os.path.splitext(config_path)[1]
|
||||
|
||||
if config_ext == '.yaml':
|
||||
return load_yaml_config_file(config_path)
|
||||
|
||||
elif config_ext == '.conf':
|
||||
return load_conf_config_file(config_path)
|
||||
return load_yaml_config_file(config_path)
|
||||
|
||||
|
||||
def load_yaml_config_file(config_path):
|
||||
@ -120,17 +107,16 @@ def load_yaml_config_file(config_path):
|
||||
import yaml
|
||||
|
||||
def parse(fname):
|
||||
""" Actually parse the file. """
|
||||
""" Parse a YAML file. """
|
||||
try:
|
||||
with open(fname) as conf_file:
|
||||
# If configuration file is empty YAML returns None
|
||||
# We convert that to an empty dict
|
||||
conf_dict = yaml.load(conf_file) or {}
|
||||
return yaml.load(conf_file) or {}
|
||||
except yaml.YAMLError:
|
||||
_LOGGER.exception('Error reading YAML configuration file %s',
|
||||
fname)
|
||||
raise HomeAssistantError()
|
||||
return conf_dict
|
||||
error = 'Error reading YAML configuration file {}'.format(fname)
|
||||
_LOGGER.exception(error)
|
||||
raise HomeAssistantError(error)
|
||||
|
||||
def yaml_include(loader, node):
|
||||
"""
|
||||
@ -153,21 +139,3 @@ def load_yaml_config_file(config_path):
|
||||
raise HomeAssistantError()
|
||||
|
||||
return conf_dict
|
||||
|
||||
|
||||
def load_conf_config_file(config_path):
|
||||
""" Parse the old style conf configuration. """
|
||||
import configparser
|
||||
|
||||
config_dict = {}
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_path)
|
||||
|
||||
for section in config.sections():
|
||||
config_dict[section] = {}
|
||||
|
||||
for key, val in config.items(section):
|
||||
config_dict[section][key] = val
|
||||
|
||||
return config_dict
|
||||
|
@ -6,10 +6,6 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME)
|
||||
from homeassistant.util import ensure_unique_string, slugify
|
||||
|
||||
# Deprecated 3/5/2015 - Moved to homeassistant.helpers.entity
|
||||
# pylint: disable=unused-import
|
||||
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
|
||||
|
||||
|
||||
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
||||
""" Generate a unique entity ID based on given entity IDs or used ids. """
|
||||
|
@ -1,10 +0,0 @@
|
||||
"""
|
||||
Deprecated since 3/21/2015 - please use helpers.entity
|
||||
"""
|
||||
import logging
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
|
||||
|
||||
logging.getLogger(__name__).warning(
|
||||
'This file is deprecated. Please use helpers.entity')
|
@ -1,10 +0,0 @@
|
||||
"""
|
||||
Deprecated since 3/21/2015 - please use helpers.entity_component
|
||||
"""
|
||||
import logging
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .entity_component import EntityComponent as DeviceComponent # noqa
|
||||
|
||||
logging.getLogger(__name__).warning(
|
||||
'This file is deprecated. Please use helpers.entity_component')
|
@ -7,6 +7,7 @@ Provides helpers for components that manage entities.
|
||||
from homeassistant.bootstrap import prepare_setup_platform
|
||||
from homeassistant.helpers import (
|
||||
generate_entity_id, config_per_platform, extract_entity_ids)
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
from homeassistant.components import group, discovery
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
|
||||
@ -115,8 +116,8 @@ class EntityComponent(object):
|
||||
|
||||
self.is_polling = True
|
||||
|
||||
self.hass.track_time_change(
|
||||
self._update_entity_states,
|
||||
track_utc_time_change(
|
||||
self.hass, self._update_entity_states,
|
||||
second=range(0, 60, self.scan_interval))
|
||||
|
||||
def _setup_platform(self, platform_type, platform_config,
|
||||
@ -135,22 +136,6 @@ class EntityComponent(object):
|
||||
self.hass, platform_config, self.add_entities, discovery_info)
|
||||
|
||||
self.hass.config.components.append(platform_name)
|
||||
|
||||
except AttributeError:
|
||||
# AttributeError if setup_platform does not exist
|
||||
# Support old deprecated method for now - 3/1/2015
|
||||
if hasattr(platform, 'get_devices'):
|
||||
self.logger.warning(
|
||||
'Please upgrade %s to return new entities using '
|
||||
'setup_platform. See %s/demo.py for an example.',
|
||||
platform_name, self.domain)
|
||||
self.add_entities(
|
||||
platform.get_devices(self.hass, platform_config))
|
||||
|
||||
else:
|
||||
self.logger.exception(
|
||||
'Error while setting up platform %s', platform_type)
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self.logger.exception(
|
||||
'Error while setting up platform %s', platform_type)
|
||||
|
163
homeassistant/helpers/event.py
Normal file
163
homeassistant/helpers/event.py
Normal file
@ -0,0 +1,163 @@
|
||||
"""
|
||||
Helpers for listening to events
|
||||
"""
|
||||
import functools as ft
|
||||
|
||||
from ..util import dt as dt_util
|
||||
from ..const import (
|
||||
ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
|
||||
|
||||
|
||||
def track_state_change(hass, entity_ids, action, from_state=None,
|
||||
to_state=None):
|
||||
"""
|
||||
Track specific state changes.
|
||||
entity_ids, from_state and to_state can be string or list.
|
||||
Use list to match multiple.
|
||||
|
||||
Returns the listener that listens on the bus for EVENT_STATE_CHANGED.
|
||||
Pass the return value into hass.bus.remove_listener to remove it.
|
||||
"""
|
||||
from_state = _process_match_param(from_state)
|
||||
to_state = _process_match_param(to_state)
|
||||
|
||||
# Ensure it is a lowercase list with entity ids we want to match on
|
||||
if isinstance(entity_ids, str):
|
||||
entity_ids = (entity_ids.lower(),)
|
||||
else:
|
||||
entity_ids = tuple(entity_id.lower() for entity_id in entity_ids)
|
||||
|
||||
@ft.wraps(action)
|
||||
def state_change_listener(event):
|
||||
""" The listener that listens for specific state changes. """
|
||||
if event.data['entity_id'] not in entity_ids:
|
||||
return
|
||||
|
||||
if 'old_state' in event.data:
|
||||
old_state = event.data['old_state'].state
|
||||
else:
|
||||
old_state = None
|
||||
|
||||
if _matcher(old_state, from_state) and \
|
||||
_matcher(event.data['new_state'].state, to_state):
|
||||
|
||||
action(event.data['entity_id'],
|
||||
event.data.get('old_state'),
|
||||
event.data['new_state'])
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, state_change_listener)
|
||||
|
||||
return state_change_listener
|
||||
|
||||
|
||||
def track_point_in_time(hass, action, point_in_time):
|
||||
"""
|
||||
Adds a listener that fires once after a spefic point in time.
|
||||
"""
|
||||
utc_point_in_time = dt_util.as_utc(point_in_time)
|
||||
|
||||
@ft.wraps(action)
|
||||
def utc_converter(utc_now):
|
||||
""" Converts passed in UTC now to local now. """
|
||||
action(dt_util.as_local(utc_now))
|
||||
|
||||
return track_point_in_utc_time(hass, utc_converter, utc_point_in_time)
|
||||
|
||||
|
||||
def track_point_in_utc_time(hass, action, point_in_time):
|
||||
"""
|
||||
Adds a listener that fires once after a specific point in UTC time.
|
||||
"""
|
||||
# Ensure point_in_time is UTC
|
||||
point_in_time = dt_util.as_utc(point_in_time)
|
||||
|
||||
@ft.wraps(action)
|
||||
def point_in_time_listener(event):
|
||||
""" Listens for matching time_changed events. """
|
||||
now = event.data[ATTR_NOW]
|
||||
|
||||
if now >= point_in_time and \
|
||||
not hasattr(point_in_time_listener, 'run'):
|
||||
|
||||
# Set variable so that we will never run twice.
|
||||
# Because the event bus might have to wait till a thread comes
|
||||
# available to execute this listener it might occur that the
|
||||
# listener gets lined up twice to be executed. This will make
|
||||
# sure the second time it does nothing.
|
||||
point_in_time_listener.run = True
|
||||
|
||||
hass.bus.remove_listener(EVENT_TIME_CHANGED,
|
||||
point_in_time_listener)
|
||||
|
||||
action(now)
|
||||
|
||||
hass.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
||||
return point_in_time_listener
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None, local=False):
|
||||
""" Adds a listener that will fire if time matches a pattern. """
|
||||
# We do not have to wrap the function with time pattern matching logic
|
||||
# if no pattern given
|
||||
if all(val is None for val in (year, month, day, hour, minute, second)):
|
||||
@ft.wraps(action)
|
||||
def time_change_listener(event):
|
||||
""" Fires every time event that comes in. """
|
||||
action(event.data[ATTR_NOW])
|
||||
|
||||
hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener)
|
||||
return time_change_listener
|
||||
|
||||
pmp = _process_match_param
|
||||
year, month, day = pmp(year), pmp(month), pmp(day)
|
||||
hour, minute, second = pmp(hour), pmp(minute), pmp(second)
|
||||
|
||||
@ft.wraps(action)
|
||||
def pattern_time_change_listener(event):
|
||||
""" Listens for matching time_changed events. """
|
||||
now = event.data[ATTR_NOW]
|
||||
|
||||
if local:
|
||||
now = dt_util.as_local(now)
|
||||
|
||||
mat = _matcher
|
||||
|
||||
if mat(now.year, year) and \
|
||||
mat(now.month, month) and \
|
||||
mat(now.day, day) and \
|
||||
mat(now.hour, hour) and \
|
||||
mat(now.minute, minute) and \
|
||||
mat(now.second, second):
|
||||
|
||||
action(now)
|
||||
|
||||
hass.bus.listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
|
||||
return pattern_time_change_listener
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_time_change(hass, action, year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None):
|
||||
""" Adds a listener that will fire if UTC time matches a pattern. """
|
||||
track_utc_time_change(hass, action, year, month, day, hour, minute, second,
|
||||
local=True)
|
||||
|
||||
|
||||
def _process_match_param(parameter):
|
||||
""" Wraps parameter in a tuple if it is not one and returns it. """
|
||||
if parameter is None or parameter == MATCH_ALL:
|
||||
return MATCH_ALL
|
||||
elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
|
||||
return (parameter,)
|
||||
else:
|
||||
return tuple(parameter)
|
||||
|
||||
|
||||
def _matcher(subject, pattern):
|
||||
""" Returns True if subject matches the pattern.
|
||||
|
||||
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
|
||||
"""
|
||||
return MATCH_ALL == pattern or subject in pattern
|
@ -30,7 +30,16 @@ class TrackStates(object):
|
||||
return self.states
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.states.extend(self.hass.states.get_since(self.now))
|
||||
self.states.extend(get_changed_since(self.hass.states.all(), self.now))
|
||||
|
||||
|
||||
def get_changed_since(states, utc_point_in_time):
|
||||
"""
|
||||
Returns all states that have been changed since utc_point_in_time.
|
||||
"""
|
||||
point_in_time = dt_util.strip_microseconds(utc_point_in_time)
|
||||
|
||||
return [state for state in states if state.last_updated >= point_in_time]
|
||||
|
||||
|
||||
def reproduce_state(hass, states, blocking=False):
|
||||
|
@ -61,11 +61,10 @@ def prepare(hass):
|
||||
# python components. If this assumption is not true, HA won't break,
|
||||
# just might output more errors.
|
||||
for fil in os.listdir(custom_path):
|
||||
if os.path.isdir(os.path.join(custom_path, fil)):
|
||||
if fil != '__pycache__':
|
||||
AVAILABLE_COMPONENTS.append(
|
||||
'custom_components.{}'.format(fil))
|
||||
|
||||
if fil == '__pycache__':
|
||||
continue
|
||||
elif os.path.isdir(os.path.join(custom_path, fil)):
|
||||
AVAILABLE_COMPONENTS.append('custom_components.{}'.format(fil))
|
||||
else:
|
||||
# For files we will strip out .py extension
|
||||
AVAILABLE_COMPONENTS.append(
|
||||
@ -195,24 +194,24 @@ def _load_order_component(comp_name, load_order, loading):
|
||||
|
||||
for dependency in component.DEPENDENCIES:
|
||||
# Check not already loaded
|
||||
if dependency not in load_order:
|
||||
# If we are already loading it, we have a circular dependency
|
||||
if dependency in loading:
|
||||
_LOGGER.error('Circular dependency detected: %s -> %s',
|
||||
comp_name, dependency)
|
||||
if dependency in load_order:
|
||||
continue
|
||||
|
||||
return OrderedSet()
|
||||
# If we are already loading it, we have a circular dependency
|
||||
if dependency in loading:
|
||||
_LOGGER.error('Circular dependency detected: %s -> %s',
|
||||
comp_name, dependency)
|
||||
return OrderedSet()
|
||||
|
||||
dep_load_order = _load_order_component(
|
||||
dependency, load_order, loading)
|
||||
dep_load_order = _load_order_component(dependency, load_order, loading)
|
||||
|
||||
# length == 0 means error loading dependency or children
|
||||
if len(dep_load_order) == 0:
|
||||
_LOGGER.error('Error loading %s dependency: %s',
|
||||
comp_name, dependency)
|
||||
return OrderedSet()
|
||||
# length == 0 means error loading dependency or children
|
||||
if len(dep_load_order) == 0:
|
||||
_LOGGER.error('Error loading %s dependency: %s',
|
||||
comp_name, dependency)
|
||||
return OrderedSet()
|
||||
|
||||
load_order.update(dep_load_order)
|
||||
load_order.update(dep_load_order)
|
||||
|
||||
load_order.add(comp_name)
|
||||
loading.remove(comp_name)
|
||||
|
@ -120,10 +120,11 @@ class HomeAssistant(ha.HomeAssistant):
|
||||
def start(self):
|
||||
# Ensure a local API exists to connect with remote
|
||||
if self.config.api is None:
|
||||
bootstrap.setup_component(self, 'http')
|
||||
bootstrap.setup_component(self, 'api')
|
||||
if not bootstrap.setup_component(self, 'api'):
|
||||
raise ha.HomeAssistantError(
|
||||
'Unable to setup local API to receive events')
|
||||
|
||||
ha.Timer(self)
|
||||
ha.create_timer(self)
|
||||
|
||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
|
||||
origin=ha.EventOrigin.remote)
|
||||
|
@ -16,11 +16,7 @@ import random
|
||||
import string
|
||||
from functools import wraps
|
||||
|
||||
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package
|
||||
# pylint: disable=unused-import
|
||||
from .dt import ( # noqa
|
||||
datetime_to_str, str_to_datetime, strip_microseconds,
|
||||
datetime_to_local_str, utcnow)
|
||||
from .dt import datetime_to_local_str, utcnow
|
||||
|
||||
|
||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||
@ -94,13 +90,12 @@ def get_local_ip():
|
||||
|
||||
# Use Google Public DNS server to determine own IP
|
||||
sock.connect(('8.8.8.8', 80))
|
||||
ip_addr = sock.getsockname()[0]
|
||||
sock.close()
|
||||
|
||||
return ip_addr
|
||||
|
||||
return sock.getsockname()[0]
|
||||
except socket.error:
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
|
||||
# Taken from http://stackoverflow.com/a/23728630
|
||||
|
@ -52,30 +52,28 @@ def mock_service(hass, domain, service):
|
||||
return calls
|
||||
|
||||
|
||||
def fire_time_changed(hass, time):
|
||||
hass.bus.fire(EVENT_TIME_CHANGED, {'now': time})
|
||||
|
||||
|
||||
def trigger_device_tracker_scan(hass):
|
||||
""" Triggers the device tracker to scan. """
|
||||
hass.bus.fire(
|
||||
EVENT_TIME_CHANGED,
|
||||
{'now':
|
||||
dt_util.utcnow().replace(second=0) + timedelta(hours=1)})
|
||||
fire_time_changed(
|
||||
hass, dt_util.utcnow().replace(second=0) + timedelta(hours=1))
|
||||
|
||||
|
||||
def ensure_sun_risen(hass):
|
||||
""" Trigger sun to rise if below horizon. """
|
||||
if not sun.is_on(hass):
|
||||
hass.bus.fire(
|
||||
EVENT_TIME_CHANGED,
|
||||
{'now':
|
||||
sun.next_rising_utc(hass) + timedelta(seconds=10)})
|
||||
if sun.is_on(hass):
|
||||
return
|
||||
fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10))
|
||||
|
||||
|
||||
def ensure_sun_set(hass):
|
||||
""" Trigger sun to set if above horizon. """
|
||||
if sun.is_on(hass):
|
||||
hass.bus.fire(
|
||||
EVENT_TIME_CHANGED,
|
||||
{'now':
|
||||
sun.next_setting_utc(hass) + timedelta(seconds=10)})
|
||||
if not sun.is_on(hass):
|
||||
return
|
||||
fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
|
||||
|
||||
|
||||
def mock_state_change_event(hass, new_state, old_state=None):
|
0
tests/components/__init__.py
Normal file
0
tests/components/__init__.py
Normal file
@ -9,7 +9,7 @@ import unittest
|
||||
import homeassistant as ha
|
||||
import homeassistant.components.demo as demo
|
||||
|
||||
from helpers import mock_http_component
|
||||
from tests.common import mock_http_component
|
||||
|
||||
|
||||
class TestDemo(unittest.TestCase):
|
@ -14,7 +14,7 @@ from homeassistant.components import (
|
||||
device_tracker, light, sun, device_sun_light_trigger)
|
||||
|
||||
|
||||
from helpers import (
|
||||
from tests.common import (
|
||||
get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
|
||||
trigger_device_tracker_scan)
|
||||
|
@ -18,7 +18,7 @@ from homeassistant.const import (
|
||||
DEVICE_DEFAULT_NAME)
|
||||
import homeassistant.components.device_tracker as device_tracker
|
||||
|
||||
from helpers import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def setUpModule(): # pylint: disable=invalid-name
|
@ -199,8 +199,7 @@ class TestComponentsGroup(unittest.TestCase):
|
||||
self.hass,
|
||||
{
|
||||
group.DOMAIN: {
|
||||
'second_group': ','.join((self.group_entity_id,
|
||||
'light.Bowl'))
|
||||
'second_group': (self.group_entity_id, 'light.Bowl')
|
||||
}
|
||||
}))
|
||||
|
@ -13,7 +13,7 @@ import homeassistant as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import history, recorder
|
||||
|
||||
from helpers import (
|
||||
from tests.common import (
|
||||
mock_http_component, mock_state_change_event, get_test_home_assistant)
|
||||
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
||||
import homeassistant.components.light as light
|
||||
|
||||
from helpers import mock_service, get_test_home_assistant
|
||||
from tests.common import mock_service, get_test_home_assistant
|
||||
|
||||
|
||||
class TestLight(unittest.TestCase):
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import logbook
|
||||
|
||||
from helpers import get_test_home_assistant, mock_http_component
|
||||
from tests.common import get_test_home_assistant, mock_http_component
|
||||
|
||||
|
||||
class TestComponentHistory(unittest.TestCase):
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID)
|
||||
import homeassistant.components.media_player as media_player
|
||||
from helpers import mock_service
|
||||
from tests.common import mock_service
|
||||
|
||||
|
||||
def setUpModule(): # pylint: disable=invalid-name
|
@ -11,7 +11,7 @@ import os
|
||||
from homeassistant.const import MATCH_ALL
|
||||
from homeassistant.components import recorder
|
||||
|
||||
from helpers import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestRecorder(unittest.TestCase):
|
@ -11,7 +11,7 @@ import homeassistant.loader as loader
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||
import homeassistant.components.switch as switch
|
||||
|
||||
from helpers import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestSwitch(unittest.TestCase):
|
@ -7,7 +7,7 @@ Provides a mock switch platform.
|
||||
Call init before using it in your tests to ensure clean test data.
|
||||
"""
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from tests.helpers import MockToggleDevice
|
||||
from tests.common import MockToggleDevice
|
||||
|
||||
|
||||
DEVICES = []
|
||||
|
@ -7,7 +7,7 @@ Provides a mock switch platform.
|
||||
Call init before using it in your tests to ensure clean test data.
|
||||
"""
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from tests.helpers import MockToggleDevice
|
||||
from tests.common import MockToggleDevice
|
||||
|
||||
|
||||
DEVICES = []
|
||||
|
0
tests/helpers/__init__.py
Normal file
0
tests/helpers/__init__.py
Normal file
126
tests/helpers/test_event.py
Normal file
126
tests/helpers/test_event.py
Normal file
@ -0,0 +1,126 @@
|
||||
"""
|
||||
tests.helpers.event_test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests event helpers.
|
||||
"""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
# pylint: disable=too-few-public-methods
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.helpers.event import *
|
||||
|
||||
|
||||
class TestEventHelpers(unittest.TestCase):
|
||||
"""
|
||||
Tests the Home Assistant event helpers.
|
||||
"""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" things to be run when tests are started. """
|
||||
self.hass = ha.HomeAssistant()
|
||||
self.hass.states.set("light.Bowl", "on")
|
||||
self.hass.states.set("switch.AC", "off")
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
def test_track_point_in_time(self):
|
||||
""" Test track point in time. """
|
||||
before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
|
||||
runs = []
|
||||
|
||||
track_point_in_utc_time(
|
||||
self.hass, lambda x: runs.append(1), birthday_paulus)
|
||||
|
||||
self._send_time_changed(before_birthday)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(runs))
|
||||
|
||||
self._send_time_changed(birthday_paulus)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
# A point in time tracker will only fire once, this should do nothing
|
||||
self._send_time_changed(birthday_paulus)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
track_point_in_time(
|
||||
self.hass, lambda x: runs.append(1), birthday_paulus)
|
||||
|
||||
self._send_time_changed(after_birthday)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(2, len(runs))
|
||||
|
||||
def test_track_time_change(self):
|
||||
""" Test tracking time change. """
|
||||
wildcard_runs = []
|
||||
specific_runs = []
|
||||
|
||||
track_time_change(self.hass, lambda x: wildcard_runs.append(1))
|
||||
track_utc_time_change(
|
||||
self.hass, lambda x: specific_runs.append(1), second=[0, 30])
|
||||
|
||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0))
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(1, len(wildcard_runs))
|
||||
|
||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15))
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(2, len(wildcard_runs))
|
||||
|
||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30))
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(2, len(specific_runs))
|
||||
self.assertEqual(3, len(wildcard_runs))
|
||||
|
||||
def test_track_state_change(self):
|
||||
""" Test track_state_change. """
|
||||
# 2 lists to track how often our callbacks get called
|
||||
specific_runs = []
|
||||
wildcard_runs = []
|
||||
|
||||
track_state_change(
|
||||
self.hass, 'light.Bowl', lambda a, b, c: specific_runs.append(1),
|
||||
'on', 'off')
|
||||
|
||||
track_state_change(
|
||||
self.hass, 'light.Bowl', lambda a, b, c: wildcard_runs.append(1),
|
||||
ha.MATCH_ALL, ha.MATCH_ALL)
|
||||
|
||||
# Set same state should not trigger a state change/listener
|
||||
self.hass.states.set('light.Bowl', 'on')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(specific_runs))
|
||||
self.assertEqual(0, len(wildcard_runs))
|
||||
|
||||
# State change off -> on
|
||||
self.hass.states.set('light.Bowl', 'off')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(1, len(wildcard_runs))
|
||||
|
||||
# State change off -> off
|
||||
self.hass.states.set('light.Bowl', 'off', {"some_attr": 1})
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(2, len(wildcard_runs))
|
||||
|
||||
# State change off -> on
|
||||
self.hass.states.set('light.Bowl', 'on')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(3, len(wildcard_runs))
|
||||
|
||||
def _send_time_changed(self, now):
|
||||
""" Send a time changed event. """
|
||||
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})
|
@ -7,7 +7,7 @@ Tests component helpers.
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import unittest
|
||||
|
||||
from helpers import get_test_home_assistant
|
||||
from common import get_test_home_assistant
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.loader as loader
|
@ -16,11 +16,10 @@ from homeassistant.const import (
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||
CONF_TIME_ZONE)
|
||||
|
||||
from helpers import get_test_config_dir
|
||||
from common import get_test_config_dir
|
||||
|
||||
CONFIG_DIR = get_test_config_dir()
|
||||
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
||||
CONF_PATH = os.path.join(CONFIG_DIR, config_util.CONF_CONFIG_FILE)
|
||||
|
||||
|
||||
def create_file(path):
|
||||
@ -51,9 +50,8 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Clean up. """
|
||||
for path in (YAML_PATH, CONF_PATH):
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
if os.path.isfile(YAML_PATH):
|
||||
os.remove(YAML_PATH)
|
||||
|
||||
def test_create_default_config(self):
|
||||
""" Test creationg of default config. """
|
||||
@ -69,21 +67,6 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||
|
||||
def test_find_config_file_conf(self):
|
||||
""" Test if it finds the old CONF config file. """
|
||||
|
||||
create_file(CONF_PATH)
|
||||
|
||||
self.assertEqual(CONF_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||
|
||||
def test_find_config_file_prefers_yaml_over_conf(self):
|
||||
""" Test if find config prefers YAML over CONF if both exist. """
|
||||
|
||||
create_file(YAML_PATH)
|
||||
create_file(CONF_PATH)
|
||||
|
||||
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||
|
||||
def test_ensure_config_exists_creates_config(self):
|
||||
""" Test that calling ensure_config_exists creates a new config file if
|
||||
none exists. """
|
||||
@ -135,20 +118,6 @@ class TestConfig(unittest.TestCase):
|
||||
self.assertEqual({'hello': 'world'},
|
||||
config_util.load_config_file(YAML_PATH))
|
||||
|
||||
def test_load_config_loads_conf_config(self):
|
||||
""" Test correct YAML config loading. """
|
||||
create_file(CONF_PATH)
|
||||
|
||||
self.assertEqual({}, config_util.load_config_file(CONF_PATH))
|
||||
|
||||
def test_conf_config_file(self):
|
||||
""" Test correct CONF config loading. """
|
||||
with open(CONF_PATH, 'w') as f:
|
||||
f.write('[ha]\ntime_zone=America/Los_Angeles')
|
||||
|
||||
self.assertEqual({'ha': {'time_zone': 'America/Los_Angeles'}},
|
||||
config_util.load_conf_config_file(CONF_PATH))
|
||||
|
||||
def test_create_default_config_detect_location(self):
|
||||
""" Test that detect location sets the correct config keys. """
|
||||
with mock.patch('homeassistant.util.location.detect_location_info',
|
||||
|
@ -8,18 +8,27 @@ Provides tests to verify that Home Assistant core works.
|
||||
# pylint: disable=too-few-public-methods
|
||||
import os
|
||||
import unittest
|
||||
import unittest.mock as mock
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||
ATTR_FRIENDLY_NAME, TEMP_CELCIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
|
||||
PST = pytz.timezone('America/Los_Angeles')
|
||||
|
||||
|
||||
class TestHomeAssistant(unittest.TestCase):
|
||||
"""
|
||||
Tests the Home Assistant core classes.
|
||||
Currently only includes tests to test cases that do not
|
||||
get tested in the API integration tests.
|
||||
"""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
@ -36,13 +45,13 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
# Already stopped after the block till stopped test
|
||||
pass
|
||||
|
||||
def test_get_config_path(self):
|
||||
""" Test get_config_path method. """
|
||||
self.assertEqual(os.path.join(os.getcwd(), "config"),
|
||||
self.hass.config.config_dir)
|
||||
|
||||
self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"),
|
||||
self.hass.config.path("test.conf"))
|
||||
def test_start(self):
|
||||
calls = []
|
||||
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
|
||||
lambda event: calls.append(1))
|
||||
self.hass.start()
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(calls))
|
||||
|
||||
def test_block_till_stoped(self):
|
||||
""" Test if we can block till stop service is called. """
|
||||
@ -51,28 +60,48 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
self.assertFalse(blocking_thread.is_alive())
|
||||
|
||||
blocking_thread.start()
|
||||
# Python will now give attention to the other thread
|
||||
time.sleep(1)
|
||||
|
||||
# Threads are unpredictable, try 20 times if we're ready
|
||||
wait_loops = 0
|
||||
while not blocking_thread.is_alive() and wait_loops < 20:
|
||||
wait_loops += 1
|
||||
time.sleep(0.05)
|
||||
|
||||
self.assertTrue(blocking_thread.is_alive())
|
||||
|
||||
self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
# hass.block_till_stopped checks every second if it should quit
|
||||
# we have to wait worst case 1 second
|
||||
# Threads are unpredictable, try 20 times if we're ready
|
||||
wait_loops = 0
|
||||
while blocking_thread.is_alive() and wait_loops < 50:
|
||||
while blocking_thread.is_alive() and wait_loops < 20:
|
||||
wait_loops += 1
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.05)
|
||||
|
||||
self.assertFalse(blocking_thread.is_alive())
|
||||
|
||||
def test_stopping_with_keyboardinterrupt(self):
|
||||
calls = []
|
||||
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
lambda event: calls.append(1))
|
||||
|
||||
def raise_keyboardinterrupt(length):
|
||||
# We don't want to patch the sleep of the timer.
|
||||
if length == 1:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
self.hass.start()
|
||||
|
||||
with mock.patch('time.sleep', raise_keyboardinterrupt):
|
||||
self.hass.block_till_stopped()
|
||||
|
||||
self.assertEqual(1, len(calls))
|
||||
|
||||
def test_track_point_in_time(self):
|
||||
""" Test track point in time. """
|
||||
before_birthday = datetime(1985, 7, 9, 12, 0, 0)
|
||||
birthday_paulus = datetime(1986, 7, 9, 12, 0, 0)
|
||||
after_birthday = datetime(1987, 7, 9, 12, 0, 0)
|
||||
before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
||||
|
||||
runs = []
|
||||
|
||||
@ -92,7 +121,7 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
self.hass.track_point_in_utc_time(
|
||||
self.hass.track_point_in_time(
|
||||
lambda x: runs.append(1), birthday_paulus)
|
||||
|
||||
self._send_time_changed(after_birthday)
|
||||
@ -105,7 +134,7 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
specific_runs = []
|
||||
|
||||
self.hass.track_time_change(lambda x: wildcard_runs.append(1))
|
||||
self.hass.track_time_change(
|
||||
self.hass.track_utc_time_change(
|
||||
lambda x: specific_runs.append(1), second=[0, 30])
|
||||
|
||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0))
|
||||
@ -130,6 +159,16 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
|
||||
class TestEvent(unittest.TestCase):
|
||||
""" Test Event class. """
|
||||
def test_eq(self):
|
||||
now = dt_util.utcnow()
|
||||
data = {'some': 'attr'}
|
||||
event1, event2 = [
|
||||
ha.Event('some_type', data, time_fired=now)
|
||||
for _ in range(2)
|
||||
]
|
||||
|
||||
self.assertEqual(event1, event2)
|
||||
|
||||
def test_repr(self):
|
||||
""" Test that repr method works. #MoreCoverage """
|
||||
self.assertEqual(
|
||||
@ -142,13 +181,27 @@ class TestEvent(unittest.TestCase):
|
||||
{"beer": "nice"},
|
||||
ha.EventOrigin.remote)))
|
||||
|
||||
def test_as_dict(self):
|
||||
event_type = 'some_type'
|
||||
now = dt_util.utcnow()
|
||||
data = {'some': 'attr'}
|
||||
|
||||
event = ha.Event(event_type, data, ha.EventOrigin.local, now)
|
||||
expected = {
|
||||
'event_type': event_type,
|
||||
'data': data,
|
||||
'origin': 'LOCAL',
|
||||
'time_fired': dt_util.datetime_to_str(now),
|
||||
}
|
||||
self.assertEqual(expected, event.as_dict())
|
||||
|
||||
|
||||
class TestEventBus(unittest.TestCase):
|
||||
""" Test EventBus methods. """
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" things to be run when tests are started. """
|
||||
self.bus = ha.EventBus()
|
||||
self.bus = ha.EventBus(ha.create_worker_pool(0))
|
||||
self.bus.listen('test_event', lambda x: len)
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
@ -157,6 +210,7 @@ class TestEventBus(unittest.TestCase):
|
||||
|
||||
def test_add_remove_listener(self):
|
||||
""" Test remove_listener method. """
|
||||
self.bus._pool.add_worker()
|
||||
old_count = len(self.bus.listeners)
|
||||
|
||||
listener = lambda x: len
|
||||
@ -182,11 +236,10 @@ class TestEventBus(unittest.TestCase):
|
||||
self.bus.listen_once('test_event', lambda x: runs.append(1))
|
||||
|
||||
self.bus.fire('test_event')
|
||||
self.bus._pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
# Second time it should not increase runs
|
||||
self.bus.fire('test_event')
|
||||
|
||||
self.bus._pool.add_worker()
|
||||
self.bus._pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
@ -200,6 +253,37 @@ class TestState(unittest.TestCase):
|
||||
ha.InvalidEntityFormatError, ha.State,
|
||||
'invalid_entity_format', 'test_state')
|
||||
|
||||
def test_domain(self):
|
||||
state = ha.State('some_domain.hello', 'world')
|
||||
self.assertEqual('some_domain', state.domain)
|
||||
|
||||
def test_object_id(self):
|
||||
state = ha.State('domain.hello', 'world')
|
||||
self.assertEqual('hello', state.object_id)
|
||||
|
||||
def test_name_if_no_friendly_name_attr(self):
|
||||
state = ha.State('domain.hello_world', 'world')
|
||||
self.assertEqual('hello world', state.name)
|
||||
|
||||
def test_name_if_friendly_name_attr(self):
|
||||
name = 'Some Unique Name'
|
||||
state = ha.State('domain.hello_world', 'world',
|
||||
{ATTR_FRIENDLY_NAME: name})
|
||||
self.assertEqual(name, state.name)
|
||||
|
||||
def test_copy(self):
|
||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
||||
self.assertEqual(state, state.copy())
|
||||
|
||||
def test_dict_conversion(self):
|
||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
||||
self.assertEqual(state, ha.State.from_dict(state.as_dict()))
|
||||
|
||||
def test_dict_conversion_with_wrong_data(self):
|
||||
self.assertIsNone(ha.State.from_dict(None))
|
||||
self.assertIsNone(ha.State.from_dict({'state': 'yes'}))
|
||||
self.assertIsNone(ha.State.from_dict({'entity_id': 'yes'}))
|
||||
|
||||
def test_repr(self):
|
||||
""" Test state.repr """
|
||||
self.assertEqual("<state happy.happy=on @ 12:00:00 08-12-1984>",
|
||||
@ -218,14 +302,15 @@ class TestStateMachine(unittest.TestCase):
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" things to be run when tests are started. """
|
||||
self.bus = ha.EventBus()
|
||||
self.pool = ha.create_worker_pool(0)
|
||||
self.bus = ha.EventBus(self.pool)
|
||||
self.states = ha.StateMachine(self.bus)
|
||||
self.states.set("light.Bowl", "on")
|
||||
self.states.set("switch.AC", "off")
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
self.bus._pool.stop()
|
||||
self.pool.stop()
|
||||
|
||||
def test_is_state(self):
|
||||
""" Test is_state method. """
|
||||
@ -244,6 +329,10 @@ class TestStateMachine(unittest.TestCase):
|
||||
self.assertEqual(1, len(ent_ids))
|
||||
self.assertTrue('light.bowl' in ent_ids)
|
||||
|
||||
def test_all(self):
|
||||
states = sorted(state.entity_id for state in self.states.all())
|
||||
self.assertEqual(['light.bowl', 'switch.ac'], states)
|
||||
|
||||
def test_remove(self):
|
||||
""" Test remove method. """
|
||||
self.assertTrue('light.bowl' in self.states.entity_ids())
|
||||
@ -255,6 +344,8 @@ class TestStateMachine(unittest.TestCase):
|
||||
|
||||
def test_track_change(self):
|
||||
""" Test states.track_change. """
|
||||
self.pool.add_worker()
|
||||
|
||||
# 2 lists to track how often our callbacks got called
|
||||
specific_runs = []
|
||||
wildcard_runs = []
|
||||
@ -291,10 +382,11 @@ class TestStateMachine(unittest.TestCase):
|
||||
self.assertEqual(3, len(wildcard_runs))
|
||||
|
||||
def test_case_insensitivty(self):
|
||||
self.pool.add_worker()
|
||||
runs = []
|
||||
|
||||
self.states.track_change(
|
||||
'light.BoWl', lambda a, b, c: runs.append(1),
|
||||
track_state_change(
|
||||
ha._MockHA(self.bus), 'light.BoWl', lambda a, b, c: runs.append(1),
|
||||
ha.MATCH_ALL, ha.MATCH_ALL)
|
||||
|
||||
self.states.set('light.BOWL', 'off')
|
||||
@ -332,16 +424,153 @@ class TestServiceRegistry(unittest.TestCase):
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" things to be run when tests are started. """
|
||||
self.pool = ha.create_worker_pool()
|
||||
self.pool = ha.create_worker_pool(0)
|
||||
self.bus = ha.EventBus(self.pool)
|
||||
self.services = ha.ServiceRegistry(self.bus, self.pool)
|
||||
self.services.register("test_domain", "test_service", lambda x: len)
|
||||
self.services.register("test_domain", "test_service", lambda x: None)
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
self.pool.stop()
|
||||
if self.pool.worker_count:
|
||||
self.pool.stop()
|
||||
|
||||
def test_has_service(self):
|
||||
""" Test has_service method. """
|
||||
self.assertTrue(
|
||||
self.services.has_service("test_domain", "test_service"))
|
||||
self.assertFalse(
|
||||
self.services.has_service("test_domain", "non_existing"))
|
||||
self.assertFalse(
|
||||
self.services.has_service("non_existing", "test_service"))
|
||||
|
||||
def test_services(self):
|
||||
expected = {
|
||||
'test_domain': ['test_service']
|
||||
}
|
||||
self.assertEqual(expected, self.services.services)
|
||||
|
||||
def test_call_with_blocking_done_in_time(self):
|
||||
self.pool.add_worker()
|
||||
self.pool.add_worker()
|
||||
calls = []
|
||||
self.services.register("test_domain", "register_calls",
|
||||
lambda x: calls.append(1))
|
||||
|
||||
self.assertTrue(
|
||||
self.services.call('test_domain', 'register_calls', blocking=True))
|
||||
self.assertEqual(1, len(calls))
|
||||
|
||||
def test_call_with_blocking_not_done_in_time(self):
|
||||
calls = []
|
||||
self.services.register("test_domain", "register_calls",
|
||||
lambda x: calls.append(1))
|
||||
|
||||
orig_limit = ha.SERVICE_CALL_LIMIT
|
||||
ha.SERVICE_CALL_LIMIT = 0.01
|
||||
self.assertFalse(
|
||||
self.services.call('test_domain', 'register_calls', blocking=True))
|
||||
self.assertEqual(0, len(calls))
|
||||
ha.SERVICE_CALL_LIMIT = orig_limit
|
||||
|
||||
def test_call_non_existing_with_blocking(self):
|
||||
self.pool.add_worker()
|
||||
self.pool.add_worker()
|
||||
orig_limit = ha.SERVICE_CALL_LIMIT
|
||||
ha.SERVICE_CALL_LIMIT = 0.01
|
||||
self.assertFalse(
|
||||
self.services.call('test_domain', 'i_do_not_exist', blocking=True))
|
||||
ha.SERVICE_CALL_LIMIT = orig_limit
|
||||
|
||||
|
||||
class TestConfig(unittest.TestCase):
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" things to be run when tests are started. """
|
||||
self.config = ha.Config()
|
||||
|
||||
def test_config_dir_set_correct(self):
|
||||
""" Test config dir set correct. """
|
||||
self.assertEqual(os.path.join(os.getcwd(), "config"),
|
||||
self.config.config_dir)
|
||||
|
||||
def test_path_with_file(self):
|
||||
""" Test get_config_path method. """
|
||||
self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"),
|
||||
self.config.path("test.conf"))
|
||||
|
||||
def test_path_with_dir_and_file(self):
|
||||
""" Test get_config_path method. """
|
||||
self.assertEqual(
|
||||
os.path.join(os.getcwd(), "config", "dir", "test.conf"),
|
||||
self.config.path("dir", "test.conf"))
|
||||
|
||||
def test_temperature_not_convert_if_no_preference(self):
|
||||
""" No unit conversion to happen if no preference. """
|
||||
self.assertEqual(
|
||||
(25, TEMP_CELCIUS),
|
||||
self.config.temperature(25, TEMP_CELCIUS))
|
||||
self.assertEqual(
|
||||
(80, TEMP_FAHRENHEIT),
|
||||
self.config.temperature(80, TEMP_FAHRENHEIT))
|
||||
|
||||
def test_temperature_not_convert_if_invalid_value(self):
|
||||
""" No unit conversion to happen if no preference. """
|
||||
self.config.temperature_unit = TEMP_FAHRENHEIT
|
||||
self.assertEqual(
|
||||
('25a', TEMP_CELCIUS),
|
||||
self.config.temperature('25a', TEMP_CELCIUS))
|
||||
|
||||
def test_temperature_not_convert_if_invalid_unit(self):
|
||||
""" No unit conversion to happen if no preference. """
|
||||
self.assertEqual(
|
||||
(25, 'Invalid unit'),
|
||||
self.config.temperature(25, 'Invalid unit'))
|
||||
|
||||
def test_temperature_to_convert_to_celcius(self):
|
||||
self.config.temperature_unit = TEMP_CELCIUS
|
||||
|
||||
self.assertEqual(
|
||||
(25, TEMP_CELCIUS),
|
||||
self.config.temperature(25, TEMP_CELCIUS))
|
||||
self.assertEqual(
|
||||
(26.7, TEMP_CELCIUS),
|
||||
self.config.temperature(80, TEMP_FAHRENHEIT))
|
||||
|
||||
def test_temperature_to_convert_to_fahrenheit(self):
|
||||
self.config.temperature_unit = TEMP_FAHRENHEIT
|
||||
|
||||
self.assertEqual(
|
||||
(77, TEMP_FAHRENHEIT),
|
||||
self.config.temperature(25, TEMP_CELCIUS))
|
||||
self.assertEqual(
|
||||
(80, TEMP_FAHRENHEIT),
|
||||
self.config.temperature(80, TEMP_FAHRENHEIT))
|
||||
|
||||
def test_as_dict(self):
|
||||
expected = {
|
||||
'latitude': None,
|
||||
'longitude': None,
|
||||
'temperature_unit': None,
|
||||
'location_name': None,
|
||||
'time_zone': 'UTC',
|
||||
'components': [],
|
||||
}
|
||||
|
||||
self.assertEqual(expected, self.config.as_dict())
|
||||
|
||||
|
||||
class TestWorkerPool(unittest.TestCase):
|
||||
def test_exception_during_job(self):
|
||||
pool = ha.create_worker_pool(1)
|
||||
|
||||
def malicious_job(_):
|
||||
raise Exception("Test breaking worker pool")
|
||||
|
||||
calls = []
|
||||
|
||||
def register_call(_):
|
||||
calls.append(1)
|
||||
|
||||
pool.add_job(ha.JobPriority.EVENT_DEFAULT, (malicious_job, None))
|
||||
pool.add_job(ha.JobPriority.EVENT_DEFAULT, (register_call, None))
|
||||
pool.block_till_done()
|
||||
self.assertEqual(1, len(calls))
|
@ -10,7 +10,7 @@ import unittest
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.components.http as http
|
||||
|
||||
from helpers import get_test_home_assistant, MockModule
|
||||
from common import get_test_home_assistant, MockModule
|
||||
|
||||
|
||||
class TestLoader(unittest.TestCase):
|
||||
|
0
tests/util/__init__.py
Normal file
0
tests/util/__init__.py
Normal file
Loading…
x
Reference in New Issue
Block a user