1
mirror of https://github.com/home-assistant/core synced 2024-07-12 07:21:24 +02:00
ha-core/homeassistant/core.py

818 lines
27 KiB
Python
Raw Normal View History

"""
2015-12-28 06:14:35 +01:00
Core components of Home Assistant.
Home Assistant is a Home Automation framework for observing the state
of entities and react to changes.
"""
2016-02-19 06:27:50 +01:00
import enum
import functools as ft
2013-09-30 09:20:27 +02:00
import logging
2016-02-19 06:27:50 +01:00
import os
import signal
2013-09-30 09:20:27 +02:00
import threading
2016-02-19 06:27:50 +01:00
import time
2016-02-10 08:27:01 +01:00
from types import MappingProxyType
import voluptuous as vol
2016-02-19 06:27:50 +01:00
import homeassistant.helpers.temperature as temp_helper
import homeassistant.util as util
import homeassistant.util.dt as dt_util
2015-09-20 18:35:03 +02:00
import homeassistant.util.location as location
from homeassistant.config import get_default_config_dir
2016-02-19 06:27:50 +01:00
from homeassistant.const import (
ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE,
ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED, MATCH_ALL, RESTART_EXIT_CODE,
2016-04-20 05:30:44 +02:00
SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, TEMP_CELSIUS,
2016-02-19 06:27:50 +01:00
TEMP_FAHRENHEIT, __version__)
from homeassistant.exceptions import (
HomeAssistantError, InvalidEntityFormatError)
from homeassistant.helpers.entity import split_entity_id, valid_entity_id
2013-09-30 09:20:27 +02:00
DOMAIN = "homeassistant"
# How often time_changed event should fire
2015-01-25 03:04:19 +01:00
TIMER_INTERVAL = 1 # seconds
# How long we wait for the result of a service call
SERVICE_CALL_LIMIT = 10 # seconds
# Define number of MINIMUM worker threads.
2015-08-03 17:05:33 +02:00
# During bootstrap of HA (see bootstrap._setup_component()) worker threads
# will be added for each component that polls devices.
MIN_WORKER_THREAD = 2
2013-11-11 23:58:57 +01:00
2014-11-08 22:57:08 +01:00
_LOGGER = logging.getLogger(__name__)
2013-11-11 23:58:57 +01:00
class HomeAssistant(object):
2015-12-28 06:14:35 +01:00
"""Root object of the Home Assistant home automation."""
2013-09-30 09:20:27 +02:00
def __init__(self):
2015-12-28 06:14:35 +01:00
"""Initialize new Home Assistant object."""
self.pool = pool = create_worker_pool()
self.bus = EventBus(pool)
self.services = ServiceRegistry(self.bus, pool)
self.states = StateMachine(self.bus)
self.config = Config()
def start(self):
2015-12-28 06:14:35 +01:00
"""Start home assistant."""
_LOGGER.info(
"Starting Home Assistant (%d threads)", self.pool.worker_count)
create_timer(self)
self.bus.fire(EVENT_HOMEASSISTANT_START)
2013-09-30 09:20:27 +02:00
def block_till_stopped(self):
2015-12-28 06:14:35 +01:00
"""Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event()
request_restart = threading.Event()
def stop_homeassistant(*args):
2015-12-28 06:14:35 +01:00
"""Stop Home Assistant."""
2015-08-03 17:05:33 +02:00
request_shutdown.set()
def restart_homeassistant(*args):
"""Reset Home Assistant."""
request_restart.set()
request_shutdown.set()
2015-08-03 17:05:33 +02:00
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant)
try:
signal.signal(signal.SIGTERM, stop_homeassistant)
except ValueError:
_LOGGER.warning(
'Could not bind to SIGTERM. Are you running in a thread?')
while not request_shutdown.isSet():
try:
time.sleep(1)
except KeyboardInterrupt:
break
2014-11-23 18:51:16 +01:00
self.stop()
return RESTART_EXIT_CODE if request_restart.isSet() else 0
2014-11-23 18:51:16 +01:00
def stop(self):
2015-12-28 06:14:35 +01:00
"""Stop Home Assistant and shuts down all threads."""
2014-11-23 18:51:16 +01:00
_LOGGER.info("Stopping")
2014-11-23 21:57:29 +01:00
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
self.pool.stop()
2014-11-23 18:51:16 +01:00
class JobPriority(util.OrderedEnum):
2015-12-28 06:14:35 +01:00
"""Provides job priorities for event bus jobs."""
2014-04-15 08:48:00 +02:00
EVENT_CALLBACK = 0
EVENT_SERVICE = 1
2014-04-15 08:48:00 +02:00
EVENT_STATE = 2
EVENT_TIME = 3
EVENT_DEFAULT = 4
@staticmethod
def from_event_type(event_type):
2015-12-28 06:14:35 +01:00
"""Return a priority based on event type."""
2014-04-15 08:48:00 +02:00
if event_type == EVENT_TIME_CHANGED:
return JobPriority.EVENT_TIME
2014-04-15 08:48:00 +02:00
elif event_type == EVENT_STATE_CHANGED:
return JobPriority.EVENT_STATE
elif event_type == EVENT_CALL_SERVICE:
return JobPriority.EVENT_SERVICE
elif event_type == EVENT_SERVICE_EXECUTED:
return JobPriority.EVENT_CALLBACK
2014-04-15 08:48:00 +02:00
else:
return JobPriority.EVENT_DEFAULT
2014-04-15 08:48:00 +02:00
class EventOrigin(enum.Enum):
2016-03-08 00:06:04 +01:00
"""Represent the origin of an event."""
local = "LOCAL"
remote = "REMOTE"
def __str__(self):
2016-03-08 00:06:04 +01:00
"""Return the event."""
return self.value
class Event(object):
2015-12-28 06:14:35 +01:00
# pylint: disable=too-few-public-methods
"""Represents an event within the Bus."""
2015-03-29 23:39:47 +02:00
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
2015-03-29 23:39:47 +02:00
def __init__(self, event_type, data=None, origin=EventOrigin.local,
time_fired=None):
2015-12-28 06:14:35 +01:00
"""Initialize a new event."""
self.event_type = event_type
self.data = data or {}
self.origin = origin
2016-04-16 09:55:35 +02:00
self.time_fired = time_fired or dt_util.utcnow()
2015-01-28 09:22:09 +01:00
def as_dict(self):
2015-12-28 06:14:35 +01:00
"""Create a dict representation of this Event."""
2015-01-28 09:22:09 +01:00
return {
'event_type': self.event_type,
'data': dict(self.data),
2015-03-29 23:39:47 +02:00
'origin': str(self.origin),
2016-04-16 09:55:35 +02:00
'time_fired': self.time_fired,
2015-01-28 09:22:09 +01:00
}
def __repr__(self):
2016-03-08 00:06:04 +01:00
"""Return the representation."""
# pylint: disable=maybe-no-member
if self.data:
return "<Event {}[{}]: {}>".format(
self.event_type, str(self.origin)[0],
util.repr_helper(self.data))
else:
return "<Event {}[{}]>".format(self.event_type,
str(self.origin)[0])
2013-11-11 01:46:48 +01:00
2015-04-30 08:21:31 +02:00
def __eq__(self, other):
2016-03-08 00:06:04 +01:00
"""Return the comparison."""
2015-04-30 08:21:31 +02:00
return (self.__class__ == other.__class__ and
self.event_type == other.event_type and
self.data == other.data and
self.origin == other.origin and
self.time_fired == other.time_fired)
2013-10-09 03:50:30 +02:00
class EventBus(object):
2015-12-28 06:14:35 +01:00
"""Allows firing of and listening for events."""
2013-09-30 09:20:27 +02:00
def __init__(self, pool=None):
2015-12-28 06:14:35 +01:00
"""Initialize a new event bus."""
self._listeners = {}
self._lock = threading.Lock()
self._pool = pool or create_worker_pool()
@property
def listeners(self):
2015-12-28 06:14:35 +01:00
"""Dict with events and the number of listeners."""
with self._lock:
return {key: len(self._listeners[key])
for key in self._listeners}
2014-01-30 07:48:35 +01:00
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
2015-12-28 06:14:35 +01:00
"""Fire an event."""
2015-04-30 08:21:31 +02:00
if not self._pool.running:
raise HomeAssistantError('Home Assistant has shut down.')
with self._lock:
# Copy the list of the current listeners because some listeners
# remove themselves as a listener while being executed which
# causes the iterator to be confused.
get = self._listeners.get
listeners = get(MATCH_ALL, []) + get(event_type, [])
2013-09-30 09:20:27 +02:00
event = Event(event_type, event_data, origin)
2015-01-28 09:22:09 +01:00
if event_type != EVENT_TIME_CHANGED:
_LOGGER.info("Bus:Handling %s", event)
if not listeners:
return
job_priority = JobPriority.from_event_type(event_type)
for func in listeners:
self._pool.add_job(job_priority, (func, event))
def listen(self, event_type, listener):
2015-12-28 06:14:35 +01:00
"""Listen for all events or events of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
as event_type.
"""
with self._lock:
if event_type in self._listeners:
self._listeners[event_type].append(listener)
2014-04-15 08:48:00 +02:00
else:
self._listeners[event_type] = [listener]
2014-11-29 08:19:59 +01:00
def listen_once(self, event_type, listener):
2015-12-28 06:14:35 +01:00
"""Listen once for event of a specific type.
2014-11-29 08:19:59 +01:00
To listen to all events specify the constant ``MATCH_ALL``
as event_type.
Returns registered listener that can be used with remove_listener.
2014-11-29 08:19:59 +01:00
"""
@ft.wraps(listener)
def onetime_listener(event):
2016-03-08 00:06:04 +01:00
"""Remove listener from eventbus and then fire listener."""
if hasattr(onetime_listener, 'run'):
return
# Set variable so that we will never run twice.
# Because the event bus might have to wait till a thread comes
# available to execute this listener it might occur that the
# listener gets lined up twice to be executed.
# This will make sure the second time it does nothing.
onetime_listener.run = True
2014-11-29 08:19:59 +01:00
self.remove_listener(event_type, onetime_listener)
2014-11-29 08:19:59 +01:00
listener(event)
2014-11-29 08:19:59 +01:00
self.listen(event_type, onetime_listener)
return onetime_listener
def remove_listener(self, event_type, listener):
2015-12-28 06:14:35 +01:00
"""Remove a listener of a specific event_type."""
with self._lock:
try:
self._listeners[event_type].remove(listener)
2013-10-24 01:29:33 +02:00
# delete event_type list if empty
if not self._listeners[event_type]:
self._listeners.pop(event_type)
2013-10-24 01:29:33 +02:00
except (KeyError, ValueError):
# KeyError is key event_type listener did not exist
# ValueError if listener did not exist within event_type
pass
2013-09-30 09:20:27 +02:00
2013-11-11 01:46:48 +01:00
class State(object):
2016-03-08 00:06:04 +01:00
"""Object to represent a state within the state machine.
entity_id: the entity that is represented.
state: the state of the entity
attributes: extra information on entity and state
last_changed: last time the state was changed, not the attributes.
last_updated: last time this object was updated.
"""
__slots__ = ['entity_id', 'state', 'attributes',
'last_changed', 'last_updated']
2014-01-23 04:40:19 +01:00
2015-04-01 16:18:03 +02:00
# pylint: disable=too-many-arguments
2015-04-01 08:08:38 +02:00
def __init__(self, entity_id, state, attributes=None, last_changed=None,
last_updated=None):
2015-12-28 06:14:35 +01:00
"""Initialize a new state."""
2016-01-24 07:37:15 +01:00
if not valid_entity_id(entity_id):
raise InvalidEntityFormatError((
"Invalid entity id encountered: {}. "
"Format should be <domain>.<object_id>").format(entity_id))
self.entity_id = entity_id.lower()
self.state = str(state)
2016-02-10 08:27:01 +01:00
self.attributes = MappingProxyType(attributes or {})
2016-04-16 09:55:35 +02:00
self.last_updated = last_updated or dt_util.utcnow()
2016-04-16 09:55:35 +02:00
self.last_changed = last_changed or self.last_updated
2015-03-17 07:32:18 +01:00
@property
def domain(self):
2015-12-28 06:14:35 +01:00
"""Domain of this state."""
2016-01-24 07:49:49 +01:00
return split_entity_id(self.entity_id)[0]
2015-03-17 07:32:18 +01:00
2015-03-29 23:39:47 +02:00
@property
def object_id(self):
2015-12-28 06:14:35 +01:00
"""Object id of this state."""
2016-01-24 07:49:49 +01:00
return split_entity_id(self.entity_id)[1]
2015-03-29 23:39:47 +02:00
@property
def name(self):
2015-12-28 06:14:35 +01:00
"""Name of this state."""
2015-03-29 23:39:47 +02:00
return (
self.attributes.get(ATTR_FRIENDLY_NAME) or
self.object_id.replace('_', ' '))
2014-01-23 04:40:19 +01:00
def as_dict(self):
2015-12-28 06:14:35 +01:00
"""Return a dict representation of the State.
2015-12-28 06:14:35 +01:00
To be used for JSON serialization.
Ensures: state == State.from_dict(state.as_dict())
"""
2014-01-23 04:40:19 +01:00
return {'entity_id': self.entity_id,
'state': self.state,
2016-02-10 08:27:01 +01:00
'attributes': dict(self.attributes),
2016-04-16 09:55:35 +02:00
'last_changed': self.last_changed,
'last_updated': self.last_updated}
2014-04-15 08:48:00 +02:00
@classmethod
def from_dict(cls, json_dict):
2015-12-28 06:14:35 +01:00
"""Initialize a state from a dict.
2015-12-28 06:14:35 +01:00
Ensures: state == State.from_json_dict(state.to_json_dict())
"""
if not (json_dict and 'entity_id' in json_dict and
'state' in json_dict):
2014-04-15 08:48:00 +02:00
return None
2014-04-15 08:48:00 +02:00
last_changed = json_dict.get('last_changed')
2016-04-16 09:55:35 +02:00
if isinstance(last_changed, str):
last_changed = dt_util.parse_datetime(last_changed)
2014-04-15 08:48:00 +02:00
2015-04-01 08:08:38 +02:00
last_updated = json_dict.get('last_updated')
2016-04-16 09:55:35 +02:00
if isinstance(last_updated, str):
last_updated = dt_util.parse_datetime(last_updated)
2015-04-01 08:08:38 +02:00
2014-04-15 08:48:00 +02:00
return cls(json_dict['entity_id'], json_dict['state'],
2015-04-01 08:08:38 +02:00
json_dict.get('attributes'), last_changed, last_updated)
def __eq__(self, other):
2016-03-08 00:06:04 +01:00
"""Return the comparison of the state."""
return (self.__class__ == other.__class__ and
self.entity_id == other.entity_id and
self.state == other.state and
self.attributes == other.attributes)
def __repr__(self):
2016-03-08 00:06:04 +01:00
"""Return the representation of the states."""
attr = "; {}".format(util.repr_helper(self.attributes)) \
if self.attributes else ""
return "<state {}={}{} @ {}>".format(
self.entity_id, self.state, attr,
2016-04-16 09:55:35 +02:00
dt_util.as_local(self.last_changed).isoformat())
2013-09-30 09:20:27 +02:00
class StateMachine(object):
2015-12-28 06:14:35 +01:00
"""Helper class that tracks the state of different entities."""
2013-09-30 09:20:27 +02:00
def __init__(self, bus):
2015-12-28 06:14:35 +01:00
"""Initialize state machine."""
2015-02-06 09:17:30 +01:00
self._states = {}
self._bus = bus
self._lock = threading.Lock()
2013-09-30 09:20:27 +02:00
2014-11-29 08:19:59 +01:00
def entity_ids(self, domain_filter=None):
2015-12-28 06:14:35 +01:00
"""List of entity ids that are being tracked."""
if domain_filter is None:
2014-11-29 08:19:59 +01:00
return list(self._states.keys())
2014-04-15 08:48:00 +02:00
domain_filter = domain_filter.lower()
with self._lock:
return [state.entity_id for state in self._states.values()
if state.domain == domain_filter]
def all(self):
2015-12-28 06:14:35 +01:00
"""Create a list of all states."""
with self._lock:
2016-02-10 08:27:01 +01:00
return list(self._states.values())
def get(self, entity_id):
2015-12-28 06:14:35 +01:00
"""Retrieve state of entity_id or None if not found."""
2016-02-10 08:27:01 +01:00
return self._states.get(entity_id.lower())
2014-04-15 08:48:00 +02:00
def is_state(self, entity_id, state):
2015-12-28 06:14:35 +01:00
"""Test if entity exists and is specified state."""
2015-02-06 09:17:30 +01:00
entity_id = entity_id.lower()
return (entity_id in self._states and
self._states[entity_id].state == state)
2013-10-24 01:08:28 +02:00
def is_state_attr(self, entity_id, name, value):
"""Test if entity exists and has a state attribute set to value."""
entity_id = entity_id.lower()
return (entity_id in self._states and
self._states[entity_id].attributes.get(name, None) == value)
def remove(self, entity_id):
2015-12-28 06:14:35 +01:00
"""Remove the state of an entity.
2015-12-28 06:14:35 +01:00
Returns boolean to indicate if an entity was removed.
"""
2015-02-06 09:17:30 +01:00
entity_id = entity_id.lower()
with self._lock:
2016-02-14 07:57:40 +01:00
old_state = self._states.pop(entity_id, None)
if old_state is None:
return False
event_data = {
'entity_id': entity_id,
'old_state': old_state,
'new_state': None,
}
self._bus.fire(EVENT_STATE_CHANGED, event_data)
return True
def set(self, entity_id, new_state, attributes=None):
2015-12-28 06:14:35 +01:00
"""Set the state of an entity, add entity if it does not exist.
Attributes is an optional dict to specify attributes of this state.
If you just update the attributes and not the state, last changed will
not be affected.
"""
2015-02-06 09:17:30 +01:00
entity_id = entity_id.lower()
2015-01-13 08:31:31 +01:00
new_state = str(new_state)
attributes = attributes or {}
2013-09-30 09:20:27 +02:00
with self._lock:
old_state = self._states.get(entity_id)
2013-09-30 09:20:27 +02:00
is_existing = old_state is not None
same_state = is_existing and old_state.state == new_state
same_attr = is_existing and old_state.attributes == attributes
if same_state and same_attr:
return
# If state did not exist or is different, set it
last_changed = old_state.last_changed if same_state else None
state = State(entity_id, new_state, attributes, last_changed)
self._states[entity_id] = state
2014-01-23 04:40:19 +01:00
2016-02-14 07:57:40 +01:00
event_data = {
'entity_id': entity_id,
'old_state': old_state,
'new_state': state,
}
self._bus.fire(EVENT_STATE_CHANGED, event_data)
2015-09-27 08:17:04 +02:00
# pylint: disable=too-few-public-methods
class Service(object):
2015-12-28 06:14:35 +01:00
"""Represents a callable service."""
2015-09-27 08:17:04 +02:00
__slots__ = ['func', 'description', 'fields', 'schema']
2015-09-27 08:17:04 +02:00
def __init__(self, func, description, fields, schema):
2015-12-28 06:14:35 +01:00
"""Initialize a service."""
2015-09-27 08:17:04 +02:00
self.func = func
self.description = description or ''
self.fields = fields or {}
self.schema = schema
2015-09-27 08:17:04 +02:00
def as_dict(self):
2015-12-28 06:14:35 +01:00
"""Return dictionary representation of this service."""
2015-09-27 08:17:04 +02:00
return {
'description': self.description,
'fields': self.fields,
}
def __call__(self, call):
2015-12-28 06:14:35 +01:00
"""Execute the service."""
try:
if self.schema:
call.data = self.schema(call.data)
self.func(call)
except vol.MultipleInvalid as ex:
_LOGGER.error('Invalid service data for %s.%s: %s',
call.domain, call.service, ex)
2015-09-27 08:17:04 +02:00
# pylint: disable=too-few-public-methods
class ServiceCall(object):
2015-12-28 06:14:35 +01:00
"""Represents a call to a service."""
__slots__ = ['domain', 'service', 'data', 'call_id']
def __init__(self, domain, service, data=None, call_id=None):
2015-12-28 06:14:35 +01:00
"""Initialize a service call."""
self.domain = domain
self.service = service
self.data = data or {}
self.call_id = call_id
def __repr__(self):
2016-03-08 00:06:04 +01:00
"""Return the represenation of the service."""
if self.data:
return "<ServiceCall {}.{}: {}>".format(
self.domain, self.service, util.repr_helper(self.data))
else:
return "<ServiceCall {}.{}>".format(self.domain, self.service)
class ServiceRegistry(object):
2015-12-28 06:14:35 +01:00
"""Offers services over the eventbus."""
def __init__(self, bus, pool=None):
2015-12-28 06:14:35 +01:00
"""Initialize a service registry."""
self._services = {}
self._lock = threading.Lock()
self._pool = pool or create_worker_pool()
self._bus = bus
self._cur_id = 0
bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call)
@property
def services(self):
2015-12-28 06:14:35 +01:00
"""Dict with per domain a list of available services."""
with self._lock:
2015-09-27 08:17:04 +02:00
return {domain: {key: value.as_dict() for key, value
in self._services[domain].items()}
for domain in self._services}
def has_service(self, domain, service):
2015-12-28 06:14:35 +01:00
"""Test if specified service exists."""
return service in self._services.get(domain, [])
# pylint: disable=too-many-arguments
def register(self, domain, service, service_func, description=None,
schema=None):
2015-09-27 08:17:04 +02:00
"""
Register a service.
Description is a dict containing key 'description' to describe
the service and a key 'fields' to describe the fields.
Schema is called to coerce and validate the service data.
2015-09-27 08:17:04 +02:00
"""
description = description or {}
service_obj = Service(service_func, description.get('description'),
description.get('fields', {}), schema)
with self._lock:
if domain in self._services:
2015-09-27 08:17:04 +02:00
self._services[domain][service] = service_obj
else:
2015-09-27 08:17:04 +02:00
self._services[domain] = {service: service_obj}
self._bus.fire(
EVENT_SERVICE_REGISTERED,
{ATTR_DOMAIN: domain, ATTR_SERVICE: service})
def call(self, domain, service, service_data=None, blocking=False):
"""
2015-12-28 06:14:35 +01:00
Call a service.
Specify blocking=True to wait till service is executed.
Waits a maximum of SERVICE_CALL_LIMIT.
If blocking = True, will return boolean if service executed
succesfully within SERVICE_CALL_LIMIT.
This method will fire an event to call the service.
This event will be picked up by this ServiceRegistry and any
other ServiceRegistry that is listening on the EventBus.
Because the service is sent as an event you are not allowed to use
the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data.
"""
call_id = self._generate_unique_id()
event_data = {
ATTR_DOMAIN: domain,
ATTR_SERVICE: service,
ATTR_SERVICE_DATA: service_data,
ATTR_SERVICE_CALL_ID: call_id,
}
if blocking:
executed_event = threading.Event()
def service_executed(call):
2015-12-28 06:14:35 +01:00
"""Callback method that is called when service is executed."""
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
executed_event.set()
self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed)
self._bus.fire(EVENT_CALL_SERVICE, event_data)
if blocking:
2015-08-04 18:13:55 +02:00
success = executed_event.wait(SERVICE_CALL_LIMIT)
self._bus.remove_listener(
EVENT_SERVICE_EXECUTED, service_executed)
return success
def _event_to_service_call(self, event):
2015-12-28 06:14:35 +01:00
"""Callback for SERVICE_CALLED events from the event bus."""
service_data = event.data.get(ATTR_SERVICE_DATA)
domain = event.data.get(ATTR_DOMAIN)
service = event.data.get(ATTR_SERVICE)
call_id = event.data.get(ATTR_SERVICE_CALL_ID)
if not self.has_service(domain, service):
return
service_handler = self._services[domain][service]
service_call = ServiceCall(domain, service, service_data, call_id)
# Add a job to the pool that calls _execute_service
self._pool.add_job(JobPriority.EVENT_SERVICE,
(self._execute_service,
(service_handler, service_call)))
def _execute_service(self, service_and_call):
2015-12-28 06:14:35 +01:00
"""Execute a service and fires a SERVICE_EXECUTED event."""
service, call = service_and_call
service(call)
if call.call_id is not None:
2015-08-11 08:34:58 +02:00
self._bus.fire(
EVENT_SERVICE_EXECUTED, {ATTR_SERVICE_CALL_ID: call.call_id})
def _generate_unique_id(self):
2015-12-28 06:14:35 +01:00
"""Generate a unique service call id."""
self._cur_id += 1
return "{}-{}".format(id(self), self._cur_id)
2013-11-11 01:46:48 +01:00
class Config(object):
2015-12-28 06:14:35 +01:00
"""Configuration settings for Home Assistant."""
# pylint: disable=too-many-instance-attributes
def __init__(self):
2015-12-28 06:14:35 +01:00
"""Initialize a new config object."""
self.latitude = None
self.longitude = None
self.temperature_unit = None
self.location_name = None
self.time_zone = None
# If True, pip install is skipped for requirements on startup
self.skip_pip = False
# List of loaded components
self.components = []
# Remote.API object pointing at local API
self.api = None
# Directory that holds the configuration
self.config_dir = get_default_config_dir()
2015-09-20 18:35:03 +02:00
def distance(self, lat, lon):
2015-12-28 06:14:35 +01:00
"""Calculate distance from Home Assistant in meters."""
2015-09-20 18:35:03 +02:00
return location.distance(self.latitude, self.longitude, lat, lon)
def path(self, *path):
2015-12-28 06:14:35 +01:00
"""Generate path to the file within the config dir."""
return os.path.join(self.config_dir, *path)
def temperature(self, value, unit):
2015-12-28 06:14:35 +01:00
"""Convert temperature to user preferred unit if set."""
2016-04-20 05:30:44 +02:00
if not (unit in (TEMP_CELSIUS, TEMP_FAHRENHEIT) and
2015-08-04 18:13:55 +02:00
self.temperature_unit and unit != self.temperature_unit):
return value, unit
try:
2015-08-17 07:06:01 +02:00
temp = float(value)
except ValueError: # Could not convert value to float
return value, unit
2015-08-17 07:06:01 +02:00
return (
round(temp_helper.convert(temp, unit, self.temperature_unit), 1),
self.temperature_unit)
2015-05-02 03:24:32 +02:00
def as_dict(self):
2015-12-28 06:14:35 +01:00
"""Create a dict representation of this dict."""
time_zone = self.time_zone or dt_util.UTC
2015-05-02 03:24:32 +02:00
return {
'latitude': self.latitude,
'longitude': self.longitude,
'temperature_unit': self.temperature_unit,
'location_name': self.location_name,
'time_zone': time_zone.zone,
2015-05-02 03:24:32 +02:00
'components': self.components,
2015-10-26 05:00:22 +01:00
'version': __version__
2015-05-02 03:24:32 +02:00
}
def create_timer(hass, interval=TIMER_INTERVAL):
2015-12-28 06:14:35 +01:00
"""Create a timer that 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 = dt_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."""
2016-03-08 01:43:33 +01:00
thread = threading.Thread(target=timer, name='Timer')
thread.daemon = True
thread.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer)
2015-09-01 09:18:26 +02:00
def create_worker_pool(worker_count=None):
2015-12-28 06:14:35 +01:00
"""Create a worker pool."""
2015-09-01 09:18:26 +02:00
if worker_count is None:
worker_count = MIN_WORKER_THREAD
def job_handler(job):
2015-12-28 06:14:35 +01:00
"""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):
2015-12-28 06:14:35 +01:00
"""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",
2016-04-16 09:55:35 +02:00
dt_util.as_local(start).isoformat(), job)
2015-08-04 18:13:55 +02:00
return util.ThreadPool(job_handler, worker_count, busy_callback)