Merge pull request #244 from balloob/cleanup

Core Cleanup
This commit is contained in:
Paulus Schoutsen 2015-08-06 05:23:23 -07:00
commit 46fd23c452
59 changed files with 1026 additions and 831 deletions

View File

@ -12,31 +12,37 @@ omit =
homeassistant/components/arduino.py homeassistant/components/arduino.py
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/*/wink.py homeassistant/components/*/wink.py
homeassistant/components/zwave.py homeassistant/components/zwave.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/*/tellstick.py
homeassistant/components/*/vera.py homeassistant/components/*/vera.py
homeassistant/components/browser.py homeassistant/components/browser.py
homeassistant/components/camera/*
homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/tomato.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/keyboard.py
homeassistant/components/light/hue.py homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py homeassistant/components/media_player/cast.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py homeassistant/components/media_player/mpd.py
homeassistant/components/notify/file.py homeassistant/components/notify/file.py
homeassistant/components/notify/instapush.py homeassistant/components/notify/instapush.py
@ -47,6 +53,7 @@ omit =
homeassistant/components/notify/syslog.py homeassistant/components/notify/syslog.py
homeassistant/components/notify/xmpp.py homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/edimax.py
homeassistant/components/sensor/efergy.py homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/mysensors.py
@ -57,8 +64,9 @@ omit =
homeassistant/components/sensor/temper.py homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.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/hikvisioncam.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/nest.py homeassistant/components/thermostat/nest.py

View File

@ -13,6 +13,7 @@ import threading
import enum import enum
import re import re
import functools as ft import functools as ft
from collections import namedtuple
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
@ -32,7 +33,7 @@ TIMER_INTERVAL = 1 # seconds
SERVICE_CALL_LIMIT = 10 # seconds SERVICE_CALL_LIMIT = 10 # seconds
# Define number of MINIMUM worker threads. # 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. # will be added for each component that polls devices.
MIN_WORKER_THREAD = 2 MIN_WORKER_THREAD = 2
@ -41,6 +42,9 @@ ENTITY_ID_PATTERN = re.compile(r"^(?P<domain>\w+)\.(?P<entity>\w+)$")
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Temporary to support deprecated methods
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
class HomeAssistant(object): class HomeAssistant(object):
""" Core class to route all communication to right components. """ """ Core class to route all communication to right components. """
@ -52,40 +56,12 @@ class HomeAssistant(object):
self.states = StateMachine(self.bus) self.states = StateMachine(self.bus)
self.config = Config() 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): def start(self):
""" Start home assistant. """ """ Start home assistant. """
_LOGGER.info( _LOGGER.info(
"Starting Home Assistant (%d threads)", self.pool.worker_count) "Starting Home Assistant (%d threads)", self.pool.worker_count)
Timer(self) create_timer(self)
self.bus.fire(EVENT_HOMEASSISTANT_START) self.bus.fire(EVENT_HOMEASSISTANT_START)
def block_till_stopped(self): def block_till_stopped(self):
@ -93,110 +69,21 @@ class HomeAssistant(object):
will block until called. """ will block until called. """
request_shutdown = threading.Event() request_shutdown = threading.Event()
self.services.register(DOMAIN, SERVICE_HOMEASSISTANT_STOP, def stop_homeassistant(service):
lambda service: request_shutdown.set()) """ Stops Home Assistant. """
request_shutdown.set()
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
while not request_shutdown.isSet(): while not request_shutdown.isSet():
try: try:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
break break
self.stop() 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): def stop(self):
""" Stops Home Assistant and shuts down all threads. """ """ Stops Home Assistant and shuts down all threads. """
_LOGGER.info("Stopping") _LOGGER.info("Stopping")
@ -208,76 +95,45 @@ class HomeAssistant(object):
self.pool.stop() self.pool.stop()
def get_entity_ids(self, domain_filter=None): def track_point_in_time(self, action, point_in_time):
""" """Deprecated method as of 8/4/2015 to track point in time."""
Returns known entity ids.
THIS METHOD IS DEPRECATED. Use hass.states.entity_ids
"""
_LOGGER.warning( _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 track_point_in_utc_time(self, action, point_in_time):
"""Deprecated method as of 8/4/2015 to track point in UTC time."""
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.
"""
_LOGGER.warning( _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, def track_time_change(self, action,
from_state=None, to_state=None): year=None, month=None, day=None,
""" hour=None, minute=None, second=None, utc=False):
Track specific state changes. """Deprecated method as of 8/4/2015 to track time change."""
entity_ids, from_state and to_state can be string or list. # pylint: disable=too-many-arguments
Use list to match multiple. _LOGGER.warning(
'hass.track_time_change is deprecated. '
THIS METHOD IS DEPRECATED. Use hass.states.track_change 'Please use homeassistant.helpers.event.track_time_change')
""" import homeassistant.helpers.event as helper
_LOGGER.warning(( helper.track_time_change(self, action, year, month, day, hour,
"hass.track_state_change is deprecated. " minute, second)
"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
class JobPriority(util.OrderedEnum): class JobPriority(util.OrderedEnum):
@ -305,33 +161,6 @@ class JobPriority(util.OrderedEnum):
return JobPriority.EVENT_DEFAULT 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): class EventOrigin(enum.Enum):
""" Distinguish between origin of event. """ """ Distinguish between origin of event. """
# pylint: disable=no-init,too-few-public-methods # pylint: disable=no-init,too-few-public-methods
@ -354,7 +183,7 @@ class Event(object):
self.event_type = event_type self.event_type = event_type
self.data = data or {} self.data = data or {}
self.origin = origin self.origin = origin
self.time_fired = util.strip_microseconds( self.time_fired = date_util.strip_microseconds(
time_fired or date_util.utcnow()) time_fired or date_util.utcnow())
def as_dict(self): def as_dict(self):
@ -446,12 +275,13 @@ class EventBus(object):
To listen to all events specify the constant ``MATCH_ALL`` To listen to all events specify the constant ``MATCH_ALL``
as event_type. 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) @ft.wraps(listener)
def onetime_listener(event): def onetime_listener(event):
""" Removes listener from eventbus and then fires listener. """ """ Removes listener from eventbus and then fires listener. """
if not hasattr(onetime_listener, 'run'): if hasattr(onetime_listener, 'run'):
return
# Set variable so that we will never run twice. # Set variable so that we will never run twice.
# Because the event bus might have to wait till a thread comes # Because the event bus might have to wait till a thread comes
# available to execute this listener it might occur that the # available to execute this listener it might occur that the
@ -465,6 +295,8 @@ class EventBus(object):
self.listen(event_type, onetime_listener) self.listen(event_type, onetime_listener)
return onetime_listener
def remove_listener(self, event_type, listener): def remove_listener(self, event_type, listener):
""" Removes a listener of a specific event_type. """ """ Removes a listener of a specific event_type. """
with self._lock: with self._lock:
@ -596,17 +428,18 @@ class StateMachine(object):
def entity_ids(self, domain_filter=None): def entity_ids(self, domain_filter=None):
""" List of entity ids that are being tracked. """ """ List of entity ids that are being tracked. """
if domain_filter is not None: if domain_filter is None:
return list(self._states.keys())
domain_filter = domain_filter.lower() domain_filter = domain_filter.lower()
return [state.entity_id for key, state return [state.entity_id for key, state
in self._states.items() in self._states.items()
if util.split_entity_id(key)[0] == domain_filter] if util.split_entity_id(key)[0] == domain_filter]
else:
return list(self._states.keys())
def all(self): def all(self):
""" Returns a list of all states. """ """ Returns a list of all states. """
with self._lock:
return [state.copy() for state in self._states.values()] return [state.copy() for state in self._states.values()]
def get(self, entity_id): def get(self, entity_id):
@ -616,16 +449,6 @@ class StateMachine(object):
# Make a copy so people won't mutate the state # Make a copy so people won't mutate the state
return state.copy() if state else None 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): def is_state(self, entity_id, state):
""" Returns True if entity exists and is specified state. """ """ Returns True if entity exists and is specified state. """
entity_id = entity_id.lower() entity_id = entity_id.lower()
@ -661,8 +484,10 @@ class StateMachine(object):
same_state = is_existing and old_state.state == new_state same_state = is_existing and old_state.state == new_state
same_attr = is_existing and old_state.attributes == attributes 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 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) state = State(entity_id, new_state, attributes, last_changed)
@ -677,43 +502,14 @@ class StateMachine(object):
def track_change(self, entity_ids, action, from_state=None, to_state=None): def track_change(self, entity_ids, action, from_state=None, to_state=None):
""" """
Track specific state changes. DEPRECATED AS OF 8/4/2015
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) _LOGGER.warning(
to_state = _process_match_param(to_state) 'hass.states.track_change is deprecated. '
'Use homeassistant.helpers.event.track_state_change instead.')
# Ensure it is a lowercase list with entity ids we want to match on import homeassistant.helpers.event as helper
if isinstance(entity_ids, str): helper.track_state_change(_MockHA(self._bus), entity_ids, action,
entity_ids = (entity_ids.lower(),) from_state, to_state)
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
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
@ -802,23 +598,15 @@ class ServiceRegistry(object):
if call.data[ATTR_SERVICE_CALL_ID] == call_id: if call.data[ATTR_SERVICE_CALL_ID] == call_id:
executed_event.set() executed_event.set()
self._bus.remove_listener(
EVENT_SERVICE_EXECUTED, service_executed)
self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed) self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed)
self._bus.fire(EVENT_CALL_SERVICE, event_data) self._bus.fire(EVENT_CALL_SERVICE, event_data)
if blocking: if blocking:
# wait will return False if event not set after our limit has success = executed_event.wait(SERVICE_CALL_LIMIT)
# passed. If not set, clean up the listener
if not executed_event.wait(SERVICE_CALL_LIMIT):
self._bus.remove_listener( self._bus.remove_listener(
EVENT_SERVICE_EXECUTED, service_executed) EVENT_SERVICE_EXECUTED, service_executed)
return success
return False
return True
def _event_to_service_call(self, event): def _event_to_service_call(self, event):
""" Calls a service from an event. """ """ Calls a service from an event. """
@ -826,15 +614,16 @@ class ServiceRegistry(object):
domain = service_data.pop(ATTR_DOMAIN, None) domain = service_data.pop(ATTR_DOMAIN, None)
service = service_data.pop(ATTR_SERVICE, None) service = service_data.pop(ATTR_SERVICE, None)
with self._lock: if not self.has_service(domain, service):
if domain in self._services and service in self._services[domain]: return
service_handler = self._services[domain][service]
service_call = ServiceCall(domain, service, service_data) service_call = ServiceCall(domain, service, service_data)
# Add a job to the pool that calls _execute_service # Add a job to the pool that calls _execute_service
self._pool.add_job(JobPriority.EVENT_SERVICE, self._pool.add_job(JobPriority.EVENT_SERVICE,
(self._execute_service, (self._execute_service,
(self._services[domain][service], (service_handler, service_call)))
service_call)))
def _execute_service(self, service_and_call): def _execute_service(self, service_and_call):
""" Executes a service and fires a SERVICE_EXECUTED event. """ """ Executes a service and fires a SERVICE_EXECUTED event. """
@ -843,9 +632,8 @@ class ServiceRegistry(object):
service(call) service(call)
self._bus.fire( self._bus.fire(
EVENT_SERVICE_EXECUTED, { EVENT_SERVICE_EXECUTED,
ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID] {ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
})
def _generate_unique_id(self): def _generate_unique_id(self):
""" Generates a unique service call id. """ """ Generates a unique service call id. """
@ -853,70 +641,6 @@ class ServiceRegistry(object):
return "{}-{}".format(id(self), self._cur_id) 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): class Config(object):
""" Configuration settings for Home Assistant. """ """ Configuration settings for Home Assistant. """
@ -943,8 +667,8 @@ class Config(object):
def temperature(self, value, unit): def temperature(self, value, unit):
""" Converts temperature to user preferred unit if set. """ """ Converts temperature to user preferred unit if set. """
if not (unit and self.temperature_unit and if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
unit != self.temperature_unit): self.temperature_unit and unit != self.temperature_unit):
return value, unit return value, unit
try: try:
@ -986,3 +710,93 @@ class InvalidEntityFormatError(HomeAssistantError):
class NoEntitySpecifiedError(HomeAssistantError): class NoEntitySpecifiedError(HomeAssistantError):
""" When no entity is specified. """ """ When no entity is specified. """
pass 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)

View File

@ -63,12 +63,15 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(component, name): def _handle_requirements(component, name):
""" Installs requirements for component. """ """ Installs requirements for component. """
if hasattr(component, 'REQUIREMENTS'): if not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS: for req in component.REQUIREMENTS:
if not pkg_util.install_package(req): if not pkg_util.install_package(req):
_LOGGER.error('Not initializing %s because could not install ' _LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req) 'dependency %s', name, req)
return False return False
return True return True
@ -83,14 +86,19 @@ def _setup_component(hass, domain, config):
_LOGGER.error( _LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s', 'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps)) domain, ", ".join(missing_deps))
return False return False
if not _handle_requirements(component, domain): if not _handle_requirements(component, domain):
return False return False
try: try:
if component.setup(hass, config): 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
hass.config.components.append(component.DOMAIN) hass.config.components.append(component.DOMAIN)
# Assumption: if a component does not depend on groups # Assumption: if a component does not depend on groups
@ -103,14 +111,6 @@ def _setup_component(hass, domain, config):
return True return True
else:
_LOGGER.error('component %s failed to initialize', domain)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
return False
def prepare_setup_platform(hass, config, domain, platform_name): def prepare_setup_platform(hass, config, domain, platform_name):
""" Loads a platform and makes sure dependencies are setup. """ """ Loads a platform and makes sure dependencies are setup. """

View File

@ -6,6 +6,7 @@ Offers state listening automation rules.
""" """
import logging import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.const import MATCH_ALL from homeassistant.const import MATCH_ALL
@ -30,7 +31,7 @@ def register(hass, config, action):
""" Listens for state changes and calls action. """ """ Listens for state changes and calls action. """
action() action()
hass.states.track_change( track_state_change(
entity_id, state_automation_listener, from_state, to_state) hass, entity_id, state_automation_listener, from_state, to_state)
return True return True

View File

@ -5,6 +5,7 @@ homeassistant.components.automation.time
Offers time listening automation rules. Offers time listening automation rules.
""" """
from homeassistant.util import convert from homeassistant.util import convert
from homeassistant.helpers.event import track_time_change
CONF_HOURS = "time_hours" CONF_HOURS = "time_hours"
CONF_MINUTES = "time_minutes" CONF_MINUTES = "time_minutes"
@ -21,8 +22,7 @@ def register(hass, config, action):
""" Listens for time changes and calls action. """ """ Listens for time changes and calls action. """
action() action()
hass.track_time_change( track_time_change(hass, time_automation_listener,
time_automation_listener,
hour=hours, minute=minutes, second=seconds) hour=hours, minute=minutes, second=seconds)
return True return True

View File

@ -8,6 +8,7 @@ the state of the sun and devices.
import logging import logging
from datetime import timedelta from datetime import timedelta
from homeassistant.helpers.event import track_point_in_time, track_state_change
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from . import light, sun, device_tracker, group from . import light, sun, device_tracker, group
@ -91,13 +92,13 @@ def setup(hass, config):
if start_point: if start_point:
for index, light_id in enumerate(light_ids): for index, light_id in enumerate(light_ids):
hass.track_point_in_time(turn_on(light_id), track_point_in_time(
(start_point + hass, turn_on(light_id),
index * LIGHT_TRANSITION_TIME)) (start_point + index * LIGHT_TRANSITION_TIME))
# Track every time sun rises so we can schedule a time-based # Track every time sun rises so we can schedule a time-based
# pre-sun set event # pre-sun set event
hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise, track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
# If the sun is already above horizon # If the sun is already above horizon
@ -157,13 +158,13 @@ def setup(hass, config):
light.turn_off(hass, light_ids) light.turn_off(hass, light_ids)
# Track home coming of each device # Track home coming of each device
hass.states.track_change( track_state_change(
device_entity_ids, check_light_on_dev_state_change, hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME) STATE_NOT_HOME, STATE_HOME)
# Track when all devices are gone to shut down lights # Track when all devices are gone to shut down lights
hass.states.track_change( track_state_change(
device_group, check_light_on_dev_state_change, hass, device_group, check_light_on_dev_state_change,
STATE_HOME, STATE_NOT_HOME) STATE_HOME, STATE_NOT_HOME)
return True return True

View File

@ -15,6 +15,7 @@ from homeassistant.helpers import validate_config
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import ( from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, DEVICE_DEFAULT_NAME) CONF_PLATFORM, DEVICE_DEFAULT_NAME)
@ -134,7 +135,7 @@ class DeviceTracker(object):
seconds = range(0, 60, seconds) seconds = range(0, 60, seconds)
_LOGGER.info("Device tracker interval second=%s", 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, hass.services.register(DOMAIN,
SERVICE_DEVICE_TRACKER_RELOAD, SERVICE_DEVICE_TRACKER_RELOAD,

View File

@ -7,6 +7,7 @@ Provides functionality to group devices that can be turned on or off.
import homeassistant as ha import homeassistant as ha
from homeassistant.helpers import generate_entity_id from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.util as util import homeassistant.util as util
from homeassistant.const import ( from homeassistant.const import (
@ -102,10 +103,6 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config): def setup(hass, config):
""" Sets up all groups found definded in the configuration. """ """ Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items(): 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) setup_group(hass, name, entity_ids)
return True return True
@ -162,8 +159,8 @@ class Group(Entity):
def start(self): def start(self):
""" Starts the tracking. """ """ Starts the tracking. """
self.hass.states.track_change( track_state_change(
self.tracking, self._state_changed_listener) self.hass, self.tracking, self._state_changed_listener)
def stop(self): def stop(self):
""" Unregisters the group from Home Assistant. """ """ Unregisters the group from Home Assistant. """

View File

@ -119,7 +119,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config=None): def setup(hass, config=None):
""" Sets up the HTTP API and debug interface. """ """ Sets up the HTTP API and debug interface. """
if config is None or DOMAIN not in config: if config is None or DOMAIN not in config:
config = {DOMAIN: {}} config = {DOMAIN: {}}
@ -139,9 +138,14 @@ def setup(hass, config=None):
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True) sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
try:
server = HomeAssistantHTTPServer( server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password, (server_host, server_port), RequestHandler, hass, api_password,
development, no_password_set, sessions_enabled) 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( hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_START, ha.EVENT_HOMEASSISTANT_START,
@ -183,10 +187,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
_LOGGER.info("running http in development mode") _LOGGER.info("running http in development mode")
def start(self): def start(self):
""" Starts the server. """ """ Starts the HTTP server. """
self.hass.bus.listen_once( def stop_http(event):
ha.EVENT_HOMEASSISTANT_STOP, """ Stops the HTTP server. """
lambda event: self.shutdown()) self.shutdown()
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
_LOGGER.info( _LOGGER.info(
"Starting web interface at http://%s:%d", *self.server_address) "Starting web interface at http://%s:%d", *self.server_address)
@ -199,7 +205,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever() self.serve_forever()
def register_path(self, method, url, callback, require_auth=True): 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)) self.paths.append((method, url, callback, require_auth))

View File

@ -166,8 +166,8 @@ def setup(hass, config):
profiles = {} profiles = {}
for profile_path in profile_paths: for profile_path in profile_paths:
if not os.path.isfile(profile_path):
if os.path.isfile(profile_path): continue
with open(profile_path) as inp: with open(profile_path) as inp:
reader = csv.reader(inp) reader = csv.reader(inp)
@ -178,7 +178,6 @@ def setup(hass, config):
for profile_id, color_x, color_y, brightness in reader: for profile_id, color_x, color_y, brightness in reader:
profiles[profile_id] = (float(color_x), float(color_y), profiles[profile_id] = (float(color_x), float(color_y),
int(brightness)) int(brightness))
except ValueError: except ValueError:
# ValueError if not 4 values per row # ValueError if not 4 values per row
# ValueError if convert to float/int failed # ValueError if convert to float/int failed
@ -206,7 +205,11 @@ def setup(hass, config):
for light in target_lights: for light in target_lights:
light.turn_off(**params) light.turn_off(**params)
else: for light in target_lights:
if light.should_poll:
light.update_ha_state(True)
return
# Processing extra data for turn light on request # Processing extra data for turn light on request
# We process the profile first so that we get the desired # We process the profile first so that we get the desired

View File

@ -98,9 +98,7 @@ ATTR_TO_PROPERTY = [
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Returns true if specified media player entity_id is on. """ Returns true if specified media player entity_id is on.
Will check all media player if no entity_id specified. """ Will check all media player if no entity_id specified. """
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_OFF) return any(not hass.states.is_state(entity_id, STATE_OFF)
for entity_id in entity_ids) for entity_id in entity_ids)
@ -108,28 +106,24 @@ def is_on(hass, entity_id=None):
def turn_on(hass, entity_id=None): def turn_on(hass, entity_id=None):
""" Will turn on specified media player or all. """ """ Will turn on specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_ON, data) hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None): def turn_off(hass, entity_id=None):
""" Will turn off specified media player or all. """ """ Will turn off specified media player or all. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def volume_up(hass, entity_id=None): def volume_up(hass, entity_id=None):
""" Send the media player the command for volume up. """ """ Send the media player the command for volume up. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data) hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
def volume_down(hass, entity_id=None): def volume_down(hass, entity_id=None):
""" Send the media player the command for volume down. """ """ Send the media player the command for volume down. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data) 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): def media_play_pause(hass, entity_id=None):
""" Send the media player the command for play/pause. """ """ Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data) hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
def media_play(hass, entity_id=None): def media_play(hass, entity_id=None):
""" Send the media player the command for play/pause. """ """ Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data) hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
def media_pause(hass, entity_id=None): def media_pause(hass, entity_id=None):
""" Send the media player the command for play/pause. """ """ Send the media player the command for play/pause. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data) hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
def media_next_track(hass, entity_id=None): def media_next_track(hass, entity_id=None):
""" Send the media player the command for next track. """ """ Send the media player the command for next track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data) hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_previous_track(hass, entity_id=None): def media_previous_track(hass, entity_id=None):
""" Send the media player the command for prev track. """ """ Send the media player the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data) 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) 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. """ """ 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: if media_id is None:
for player in target_players: return
for player in component.extract_from_service(service):
player.play_youtube(media_id) player.play_youtube(media_id)
if player.should_poll: if player.should_poll:
player.update_ha_state(True) player.update_ha_state(True)
hass.services.register(DOMAIN, "start_fireplace", hass.services.register(
lambda service: DOMAIN, "start_fireplace",
play_youtube_video_service(service, "eyU3bRy2x44")) lambda service: play_youtube_video_service(service, "eyU3bRy2x44"))
hass.services.register(DOMAIN, "start_epic_sax", hass.services.register(
lambda service: DOMAIN, "start_epic_sax",
play_youtube_video_service(service, "kxopViU98Xo")) lambda service: play_youtube_video_service(service, "kxopViU98Xo"))
hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO, hass.services.register(
lambda service: DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service)
play_youtube_video_service(
service, service.data.get('video')))
return True return True

View File

@ -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

View File

@ -19,6 +19,7 @@ import logging
from collections import namedtuple from collections import namedtuple
from homeassistant import State from homeassistant import State
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.state import reproduce_state from homeassistant.helpers.state import reproduce_state
@ -104,8 +105,8 @@ class Scene(ToggleEntity):
self.prev_states = None self.prev_states = None
self.ignore_updates = False self.ignore_updates = False
self.hass.states.track_change( track_state_change(
self.entity_ids, self.entity_state_changed) self.hass, self.entity_ids, self.entity_state_changed)
self.update() self.update()

View File

@ -17,6 +17,7 @@ from datetime import timedelta
import logging import logging
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components.scheduler import ServiceEventListener from homeassistant.components.scheduler import ServiceEventListener
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -62,7 +63,7 @@ class TimeEventListener(ServiceEventListener):
""" Call the execute method """ """ Call the execute method """
self.execute(hass) self.execute(hass)
hass.track_point_in_time(execute, next_time) track_point_in_time(hass, execute, next_time)
_LOGGER.info( _LOGGER.info(
'TimeEventListener scheduled for %s, will call service %s.%s', 'TimeEventListener scheduled for %s, will call service %s.%s',

View File

@ -10,6 +10,7 @@ from datetime import timedelta
import homeassistant.util.dt as date_util import homeassistant.util.dt as date_util
import threading import threading
from homeassistant.helpers.event import track_point_in_time
from homeassistant.util import split_entity_id from homeassistant.util import split_entity_id
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, EVENT_TIME_CHANGED) 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: elif CONF_DELAY in action:
delay = timedelta(**action[CONF_DELAY]) delay = timedelta(**action[CONF_DELAY])
point_in_time = date_util.now() + delay point_in_time = date_util.now() + delay
self.listener = self.hass.track_point_in_time( self.listener = track_point_in_time(
self, point_in_time) self.hass, self, point_in_time)
return False return False
return True return True

View File

@ -9,6 +9,7 @@ Provides a simple alarm feature:
import logging import logging
import homeassistant.loader as loader 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 from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
DOMAIN = "simple_alarm" DOMAIN = "simple_alarm"
@ -83,8 +84,8 @@ def setup(hass, config):
if not device_tracker.is_on(hass): if not device_tracker.is_on(hass):
unknown_alarm() unknown_alarm()
hass.states.track_change( track_state_change(
light.ENTITY_ID_ALL_LIGHTS, hass, light.ENTITY_ID_ALL_LIGHTS,
unknown_alarm_if_lights_on, STATE_OFF, STATE_ON) unknown_alarm_if_lights_on, STATE_OFF, STATE_ON)
def ring_known_alarm(entity_id, old_state, new_state): def ring_known_alarm(entity_id, old_state, new_state):
@ -93,8 +94,8 @@ def setup(hass, config):
known_alarm() known_alarm()
# Track home coming of each device # Track home coming of each device
hass.states.track_change( track_state_change(
hass.states.entity_ids(device_tracker.DOMAIN), hass, hass.states.entity_ids(device_tracker.DOMAIN),
ring_known_alarm, STATE_NOT_HOME, STATE_HOME) ring_known_alarm, STATE_NOT_HOME, STATE_HOME)
return True return True

View File

@ -21,9 +21,12 @@ The sun event need to have the type 'sun', which service to call, which event
""" """
import logging import logging
from datetime import timedelta from datetime import timedelta
import urllib
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import (
track_point_in_utc_time, track_point_in_time)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.components.scheduler import ServiceEventListener from homeassistant.components.scheduler import ServiceEventListener
@ -129,8 +132,13 @@ def setup(hass, config):
if elevation is None: if elevation is None:
google = GoogleGeocoder() google = GoogleGeocoder()
try:
google._get_elevation(location) # pylint: disable=protected-access google._get_elevation(location) # pylint: disable=protected-access
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation) _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 = Sun(hass, location)
sun.point_in_time_listener(dt_util.utcnow()) sun.point_in_time_listener(dt_util.utcnow())
@ -203,8 +211,8 @@ class Sun(Entity):
self.update_ha_state() self.update_ha_state()
# Schedule next update at next_change+1 second so sun state has changed # Schedule next update at next_change+1 second so sun state has changed
self.hass.track_point_in_utc_time( track_point_in_utc_time(
self.point_in_time_listener, self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1)) self.next_change + timedelta(seconds=1))
@ -266,7 +274,7 @@ class SunEventListener(ServiceEventListener):
""" Call the execute method. """ """ Call the execute method. """
self.execute(hass) self.execute(hass)
hass.track_point_in_time(execute, next_time) track_point_in_time(hass, execute, next_time)
return next_time return next_time

View File

@ -45,21 +45,18 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Returns if the switch is on based on the statemachine. """ """ Returns if the switch is on based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
return hass.states.is_state(entity_id, STATE_ON) return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id=None): def turn_on(hass, entity_id=None):
""" Turns all or specified switch on. """ """ Turns all or specified switch on. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_ON, data) hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def turn_off(hass, entity_id=None): def turn_off(hass, entity_id=None):
""" Turns all or specified switch off. """ """ Turns all or specified switch off. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
@ -84,7 +81,6 @@ def setup(hass, config):
switch.update_ha_state(True) switch.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)
return True return True

View File

@ -64,6 +64,7 @@ import homeassistant.components as core
import homeassistant.util as util import homeassistant.util as util
from homeassistant.components.thermostat import ThermostatDevice from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.helpers.event import track_state_change
from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF from homeassistant.const import TEMP_CELCIUS, STATE_ON, STATE_OFF
TOL_TEMP = 0.3 TOL_TEMP = 0.3
@ -108,10 +109,10 @@ class HeatControl(ThermostatDevice):
self._away = False self._away = False
self._heater_manual_changed = True self._heater_manual_changed = True
hass.states.track_change(self.heater_entity_id, track_state_change(hass, self.heater_entity_id,
self._heater_turned_on, self._heater_turned_on,
STATE_OFF, STATE_ON) STATE_OFF, STATE_ON)
hass.states.track_change(self.heater_entity_id, track_state_change(hass, self.heater_entity_id,
self._heater_turned_off, self._heater_turned_off,
STATE_ON, STATE_OFF) STATE_ON, STATE_OFF)

View File

@ -13,14 +13,11 @@ from homeassistant.const import (
CONF_TIME_ZONE) CONF_TIME_ZONE)
import homeassistant.util.location as loc_util import homeassistant.util.location as loc_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
YAML_CONFIG_FILE = 'configuration.yaml' YAML_CONFIG_FILE = 'configuration.yaml'
CONF_CONFIG_FILE = 'home-assistant.conf'
DEFAULT_CONFIG = [ DEFAULT_CONFIG = (
# Tuples (attribute, default, auto detect property, description) # Tuples (attribute, default, auto detect property, description)
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is ' (CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
'running'), 'running'),
@ -30,9 +27,9 @@ DEFAULT_CONFIG = [
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celcius, F for Fahrenheit'), (CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celcius, F for Fahrenheit'),
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
'pedia.org/wiki/List_of_tz_database_time_zones'), 'pedia.org/wiki/List_of_tz_database_time_zones'),
] )
DEFAULT_COMPONENTS = [ DEFAULT_COMPONENTS = (
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun'] 'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun')
def ensure_config_exists(config_dir, detect_location=True): def ensure_config_exists(config_dir, detect_location=True):
@ -95,42 +92,31 @@ def create_default_config(config_dir, detect_location=True):
def find_config_file(config_dir): def find_config_file(config_dir):
""" Looks in given directory for supported config files. """ """ Looks in given directory for supported config files. """
for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE): config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
config_path = os.path.join(config_dir, filename)
if os.path.isfile(config_path): return config_path if os.path.isfile(config_path) else None
return config_path
return None
def load_config_file(config_path): def load_config_file(config_path):
""" Loads given config file. """ """ Loads given config file. """
config_ext = os.path.splitext(config_path)[1]
if config_ext == '.yaml':
return load_yaml_config_file(config_path) return load_yaml_config_file(config_path)
elif config_ext == '.conf':
return load_conf_config_file(config_path)
def load_yaml_config_file(config_path): def load_yaml_config_file(config_path):
""" Parse a YAML configuration file. """ """ Parse a YAML configuration file. """
import yaml import yaml
def parse(fname): def parse(fname):
""" Actually parse the file. """ """ Parse a YAML file. """
try: try:
with open(fname) as conf_file: with open(fname) as conf_file:
# If configuration file is empty YAML returns None # If configuration file is empty YAML returns None
# We convert that to an empty dict # We convert that to an empty dict
conf_dict = yaml.load(conf_file) or {} return yaml.load(conf_file) or {}
except yaml.YAMLError: except yaml.YAMLError:
_LOGGER.exception('Error reading YAML configuration file %s', error = 'Error reading YAML configuration file {}'.format(fname)
fname) _LOGGER.exception(error)
raise HomeAssistantError() raise HomeAssistantError(error)
return conf_dict
def yaml_include(loader, node): def yaml_include(loader, node):
""" """
@ -153,21 +139,3 @@ def load_yaml_config_file(config_path):
raise HomeAssistantError() raise HomeAssistantError()
return conf_dict 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

View File

@ -6,10 +6,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME) ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.util import ensure_unique_string, slugify 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): 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. """ """ Generate a unique entity ID based on given entity IDs or used ids. """

View File

@ -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')

View File

@ -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')

View File

@ -7,6 +7,7 @@ Provides helpers for components that manage entities.
from homeassistant.bootstrap import prepare_setup_platform from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import ( from homeassistant.helpers import (
generate_entity_id, config_per_platform, extract_entity_ids) 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.components import group, discovery
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
@ -115,8 +116,8 @@ class EntityComponent(object):
self.is_polling = True self.is_polling = True
self.hass.track_time_change( track_utc_time_change(
self._update_entity_states, self.hass, self._update_entity_states,
second=range(0, 60, self.scan_interval)) second=range(0, 60, self.scan_interval))
def _setup_platform(self, platform_type, platform_config, 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, platform_config, self.add_entities, discovery_info)
self.hass.config.components.append(platform_name) 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 except Exception: # pylint: disable=broad-except
self.logger.exception( self.logger.exception(
'Error while setting up platform %s', platform_type) 'Error while setting up platform %s', platform_type)

View 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

View File

@ -30,7 +30,16 @@ class TrackStates(object):
return self.states return self.states
def __exit__(self, exc_type, exc_value, traceback): 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): def reproduce_state(hass, states, blocking=False):

View File

@ -61,11 +61,10 @@ def prepare(hass):
# python components. If this assumption is not true, HA won't break, # python components. If this assumption is not true, HA won't break,
# just might output more errors. # just might output more errors.
for fil in os.listdir(custom_path): for fil in os.listdir(custom_path):
if os.path.isdir(os.path.join(custom_path, fil)): if fil == '__pycache__':
if fil != '__pycache__': continue
AVAILABLE_COMPONENTS.append( elif os.path.isdir(os.path.join(custom_path, fil)):
'custom_components.{}'.format(fil)) AVAILABLE_COMPONENTS.append('custom_components.{}'.format(fil))
else: else:
# For files we will strip out .py extension # For files we will strip out .py extension
AVAILABLE_COMPONENTS.append( AVAILABLE_COMPONENTS.append(
@ -195,16 +194,16 @@ def _load_order_component(comp_name, load_order, loading):
for dependency in component.DEPENDENCIES: for dependency in component.DEPENDENCIES:
# Check not already loaded # Check not already loaded
if dependency not in load_order: if dependency in load_order:
continue
# If we are already loading it, we have a circular dependency # If we are already loading it, we have a circular dependency
if dependency in loading: if dependency in loading:
_LOGGER.error('Circular dependency detected: %s -> %s', _LOGGER.error('Circular dependency detected: %s -> %s',
comp_name, dependency) comp_name, dependency)
return OrderedSet() return OrderedSet()
dep_load_order = _load_order_component( dep_load_order = _load_order_component(dependency, load_order, loading)
dependency, load_order, loading)
# length == 0 means error loading dependency or children # length == 0 means error loading dependency or children
if len(dep_load_order) == 0: if len(dep_load_order) == 0:

View File

@ -120,10 +120,11 @@ class HomeAssistant(ha.HomeAssistant):
def start(self): def start(self):
# Ensure a local API exists to connect with remote # Ensure a local API exists to connect with remote
if self.config.api is None: if self.config.api is None:
bootstrap.setup_component(self, 'http') if not bootstrap.setup_component(self, 'api'):
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, self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
origin=ha.EventOrigin.remote) origin=ha.EventOrigin.remote)

View File

@ -16,11 +16,7 @@ import random
import string import string
from functools import wraps from functools import wraps
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package from .dt import datetime_to_local_str, utcnow
# pylint: disable=unused-import
from .dt import ( # noqa
datetime_to_str, str_to_datetime, strip_microseconds,
datetime_to_local_str, utcnow)
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
@ -94,13 +90,12 @@ def get_local_ip():
# Use Google Public DNS server to determine own IP # Use Google Public DNS server to determine own IP
sock.connect(('8.8.8.8', 80)) sock.connect(('8.8.8.8', 80))
ip_addr = sock.getsockname()[0]
sock.close()
return ip_addr
return sock.getsockname()[0]
except socket.error: except socket.error:
return socket.gethostbyname(socket.gethostname()) return socket.gethostbyname(socket.gethostname())
finally:
sock.close()
# Taken from http://stackoverflow.com/a/23728630 # Taken from http://stackoverflow.com/a/23728630

View File

@ -52,30 +52,28 @@ def mock_service(hass, domain, service):
return calls return calls
def fire_time_changed(hass, time):
hass.bus.fire(EVENT_TIME_CHANGED, {'now': time})
def trigger_device_tracker_scan(hass): def trigger_device_tracker_scan(hass):
""" Triggers the device tracker to scan. """ """ Triggers the device tracker to scan. """
hass.bus.fire( fire_time_changed(
EVENT_TIME_CHANGED, hass, dt_util.utcnow().replace(second=0) + timedelta(hours=1))
{'now':
dt_util.utcnow().replace(second=0) + timedelta(hours=1)})
def ensure_sun_risen(hass): def ensure_sun_risen(hass):
""" Trigger sun to rise if below horizon. """ """ Trigger sun to rise if below horizon. """
if not sun.is_on(hass): if sun.is_on(hass):
hass.bus.fire( return
EVENT_TIME_CHANGED, fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10))
{'now':
sun.next_rising_utc(hass) + timedelta(seconds=10)})
def ensure_sun_set(hass): def ensure_sun_set(hass):
""" Trigger sun to set if above horizon. """ """ Trigger sun to set if above horizon. """
if sun.is_on(hass): if not sun.is_on(hass):
hass.bus.fire( return
EVENT_TIME_CHANGED, fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
{'now':
sun.next_setting_utc(hass) + timedelta(seconds=10)})
def mock_state_change_event(hass, new_state, old_state=None): def mock_state_change_event(hass, new_state, old_state=None):

View File

View File

@ -9,7 +9,7 @@ import unittest
import homeassistant as ha import homeassistant as ha
import homeassistant.components.demo as demo import homeassistant.components.demo as demo
from helpers import mock_http_component from tests.common import mock_http_component
class TestDemo(unittest.TestCase): class TestDemo(unittest.TestCase):

View File

@ -14,7 +14,7 @@ from homeassistant.components import (
device_tracker, light, sun, device_sun_light_trigger) 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, get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
trigger_device_tracker_scan) trigger_device_tracker_scan)

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME) DEVICE_DEFAULT_NAME)
import homeassistant.components.device_tracker as device_tracker 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 def setUpModule(): # pylint: disable=invalid-name

View File

@ -199,8 +199,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass, self.hass,
{ {
group.DOMAIN: { group.DOMAIN: {
'second_group': ','.join((self.group_entity_id, 'second_group': (self.group_entity_id, 'light.Bowl')
'light.Bowl'))
} }
})) }))

View File

@ -13,7 +13,7 @@ import homeassistant as ha
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components import history, recorder from homeassistant.components import history, recorder
from helpers import ( from tests.common import (
mock_http_component, mock_state_change_event, get_test_home_assistant) mock_http_component, mock_state_change_event, get_test_home_assistant)

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF) SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components.light as light 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): class TestLight(unittest.TestCase):

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components import logbook 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): class TestComponentHistory(unittest.TestCase):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID) SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID)
import homeassistant.components.media_player as media_player 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 def setUpModule(): # pylint: disable=invalid-name

View File

@ -11,7 +11,7 @@ import os
from homeassistant.const import MATCH_ALL from homeassistant.const import MATCH_ALL
from homeassistant.components import recorder from homeassistant.components import recorder
from helpers import get_test_home_assistant from tests.common import get_test_home_assistant
class TestRecorder(unittest.TestCase): class TestRecorder(unittest.TestCase):

View File

@ -11,7 +11,7 @@ import homeassistant.loader as loader
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
import homeassistant.components.switch as switch 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): class TestSwitch(unittest.TestCase):

View File

@ -7,7 +7,7 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data. Call init before using it in your tests to ensure clean test data.
""" """
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
from tests.helpers import MockToggleDevice from tests.common import MockToggleDevice
DEVICES = [] DEVICES = []

View File

@ -7,7 +7,7 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data. Call init before using it in your tests to ensure clean test data.
""" """
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
from tests.helpers import MockToggleDevice from tests.common import MockToggleDevice
DEVICES = [] DEVICES = []

View File

126
tests/helpers/test_event.py Normal file
View 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})

View File

@ -7,7 +7,7 @@ Tests component helpers.
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
import unittest import unittest
from helpers import get_test_home_assistant from common import get_test_home_assistant
import homeassistant as ha import homeassistant as ha
import homeassistant.loader as loader import homeassistant.loader as loader

View File

@ -16,11 +16,10 @@ from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
CONF_TIME_ZONE) CONF_TIME_ZONE)
from helpers import get_test_config_dir from common import get_test_config_dir
CONFIG_DIR = get_test_config_dir() CONFIG_DIR = get_test_config_dir()
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) 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): def create_file(path):
@ -51,9 +50,8 @@ class TestConfig(unittest.TestCase):
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
""" Clean up. """ """ Clean up. """
for path in (YAML_PATH, CONF_PATH): if os.path.isfile(YAML_PATH):
if os.path.isfile(path): os.remove(YAML_PATH)
os.remove(path)
def test_create_default_config(self): def test_create_default_config(self):
""" Test creationg of default config. """ """ Test creationg of default config. """
@ -69,21 +67,6 @@ class TestConfig(unittest.TestCase):
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) 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): def test_ensure_config_exists_creates_config(self):
""" Test that calling ensure_config_exists creates a new config file if """ Test that calling ensure_config_exists creates a new config file if
none exists. """ none exists. """
@ -135,20 +118,6 @@ class TestConfig(unittest.TestCase):
self.assertEqual({'hello': 'world'}, self.assertEqual({'hello': 'world'},
config_util.load_config_file(YAML_PATH)) 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): def test_create_default_config_detect_location(self):
""" Test that detect location sets the correct config keys. """ """ Test that detect location sets the correct config keys. """
with mock.patch('homeassistant.util.location.detect_location_info', with mock.patch('homeassistant.util.location.detect_location_info',

View File

@ -8,18 +8,27 @@ Provides tests to verify that Home Assistant core works.
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import os import os
import unittest import unittest
import unittest.mock as mock
import time import time
import threading import threading
from datetime import datetime from datetime import datetime
import pytz
import homeassistant as ha 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): class TestHomeAssistant(unittest.TestCase):
""" """
Tests the Home Assistant core classes. 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 def setUp(self): # pylint: disable=invalid-name
@ -36,13 +45,13 @@ class TestHomeAssistant(unittest.TestCase):
# Already stopped after the block till stopped test # Already stopped after the block till stopped test
pass pass
def test_get_config_path(self): def test_start(self):
""" Test get_config_path method. """ calls = []
self.assertEqual(os.path.join(os.getcwd(), "config"), self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
self.hass.config.config_dir) lambda event: calls.append(1))
self.hass.start()
self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"), self.hass.pool.block_till_done()
self.hass.config.path("test.conf")) self.assertEqual(1, len(calls))
def test_block_till_stoped(self): def test_block_till_stoped(self):
""" Test if we can block till stop service is called. """ """ Test if we can block till stop service is called. """
@ -51,28 +60,48 @@ class TestHomeAssistant(unittest.TestCase):
self.assertFalse(blocking_thread.is_alive()) self.assertFalse(blocking_thread.is_alive())
blocking_thread.start() 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.assertTrue(blocking_thread.is_alive())
self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP) self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP)
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
# hass.block_till_stopped checks every second if it should quit # Threads are unpredictable, try 20 times if we're ready
# we have to wait worst case 1 second
wait_loops = 0 wait_loops = 0
while blocking_thread.is_alive() and wait_loops < 50: while blocking_thread.is_alive() and wait_loops < 20:
wait_loops += 1 wait_loops += 1
time.sleep(0.1) time.sleep(0.05)
self.assertFalse(blocking_thread.is_alive()) 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): def test_track_point_in_time(self):
""" Test track point in time. """ """ Test track point in time. """
before_birthday = datetime(1985, 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) birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
after_birthday = datetime(1987, 7, 9, 12, 0, 0) after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
runs = [] runs = []
@ -92,7 +121,7 @@ class TestHomeAssistant(unittest.TestCase):
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(runs)) 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) lambda x: runs.append(1), birthday_paulus)
self._send_time_changed(after_birthday) self._send_time_changed(after_birthday)
@ -105,7 +134,7 @@ class TestHomeAssistant(unittest.TestCase):
specific_runs = [] specific_runs = []
self.hass.track_time_change(lambda x: wildcard_runs.append(1)) 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]) lambda x: specific_runs.append(1), second=[0, 30])
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0))
@ -130,6 +159,16 @@ class TestHomeAssistant(unittest.TestCase):
class TestEvent(unittest.TestCase): class TestEvent(unittest.TestCase):
""" Test Event class. """ """ 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): def test_repr(self):
""" Test that repr method works. #MoreCoverage """ """ Test that repr method works. #MoreCoverage """
self.assertEqual( self.assertEqual(
@ -142,13 +181,27 @@ class TestEvent(unittest.TestCase):
{"beer": "nice"}, {"beer": "nice"},
ha.EventOrigin.remote))) 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): class TestEventBus(unittest.TestCase):
""" Test EventBus methods. """ """ Test EventBus methods. """
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """ """ 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) self.bus.listen('test_event', lambda x: len)
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
@ -157,6 +210,7 @@ class TestEventBus(unittest.TestCase):
def test_add_remove_listener(self): def test_add_remove_listener(self):
""" Test remove_listener method. """ """ Test remove_listener method. """
self.bus._pool.add_worker()
old_count = len(self.bus.listeners) old_count = len(self.bus.listeners)
listener = lambda x: len 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.listen_once('test_event', lambda x: runs.append(1))
self.bus.fire('test_event') self.bus.fire('test_event')
self.bus._pool.block_till_done()
self.assertEqual(1, len(runs))
# Second time it should not increase runs # Second time it should not increase runs
self.bus.fire('test_event') self.bus.fire('test_event')
self.bus._pool.add_worker()
self.bus._pool.block_till_done() self.bus._pool.block_till_done()
self.assertEqual(1, len(runs)) self.assertEqual(1, len(runs))
@ -200,6 +253,37 @@ class TestState(unittest.TestCase):
ha.InvalidEntityFormatError, ha.State, ha.InvalidEntityFormatError, ha.State,
'invalid_entity_format', 'test_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): def test_repr(self):
""" Test state.repr """ """ Test state.repr """
self.assertEqual("<state happy.happy=on @ 12:00:00 08-12-1984>", 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 def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """ """ 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 = ha.StateMachine(self.bus)
self.states.set("light.Bowl", "on") self.states.set("light.Bowl", "on")
self.states.set("switch.AC", "off") self.states.set("switch.AC", "off")
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """ """ Stop down stuff we started. """
self.bus._pool.stop() self.pool.stop()
def test_is_state(self): def test_is_state(self):
""" Test is_state method. """ """ Test is_state method. """
@ -244,6 +329,10 @@ class TestStateMachine(unittest.TestCase):
self.assertEqual(1, len(ent_ids)) self.assertEqual(1, len(ent_ids))
self.assertTrue('light.bowl' in 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): def test_remove(self):
""" Test remove method. """ """ Test remove method. """
self.assertTrue('light.bowl' in self.states.entity_ids()) self.assertTrue('light.bowl' in self.states.entity_ids())
@ -255,6 +344,8 @@ class TestStateMachine(unittest.TestCase):
def test_track_change(self): def test_track_change(self):
""" Test states.track_change. """ """ Test states.track_change. """
self.pool.add_worker()
# 2 lists to track how often our callbacks got called # 2 lists to track how often our callbacks got called
specific_runs = [] specific_runs = []
wildcard_runs = [] wildcard_runs = []
@ -291,10 +382,11 @@ class TestStateMachine(unittest.TestCase):
self.assertEqual(3, len(wildcard_runs)) self.assertEqual(3, len(wildcard_runs))
def test_case_insensitivty(self): def test_case_insensitivty(self):
self.pool.add_worker()
runs = [] runs = []
self.states.track_change( track_state_change(
'light.BoWl', lambda a, b, c: runs.append(1), ha._MockHA(self.bus), 'light.BoWl', lambda a, b, c: runs.append(1),
ha.MATCH_ALL, ha.MATCH_ALL) ha.MATCH_ALL, ha.MATCH_ALL)
self.states.set('light.BOWL', 'off') self.states.set('light.BOWL', 'off')
@ -332,16 +424,153 @@ class TestServiceRegistry(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """ """ 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.bus = ha.EventBus(self.pool)
self.services = ha.ServiceRegistry(self.bus, 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 def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """ """ Stop down stuff we started. """
if self.pool.worker_count:
self.pool.stop() self.pool.stop()
def test_has_service(self): def test_has_service(self):
""" Test has_service method. """ """ Test has_service method. """
self.assertTrue( self.assertTrue(
self.services.has_service("test_domain", "test_service")) 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))

View File

@ -10,7 +10,7 @@ import unittest
import homeassistant.loader as loader import homeassistant.loader as loader
import homeassistant.components.http as http 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): class TestLoader(unittest.TestCase):

0
tests/util/__init__.py Normal file
View File