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

1275 lines
41 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.
"""
# pylint: disable=unused-import, too-many-lines
import asyncio
from concurrent.futures import ThreadPoolExecutor
2016-02-19 06:27:50 +01:00
import enum
2013-09-30 09:20:27 +02:00
import logging
2016-02-19 06:27:50 +01:00
import os
2016-08-09 05:21:40 +02:00
import re
import signal
import sys
import threading
2016-02-19 06:27:50 +01:00
import time
2016-02-10 08:27:01 +01:00
from types import MappingProxyType
2016-08-09 05:21:40 +02:00
from typing import Optional, Any, Callable, List # NOQA
import aiohttp
import voluptuous as vol
from voluptuous.humanize import humanize_error
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,
Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc
2016-07-31 22:24:49 +02:00
SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, __version__)
2016-02-19 06:27:50 +01:00
from homeassistant.exceptions import (
HomeAssistantError, InvalidEntityFormatError)
from homeassistant.util.async import (
run_coroutine_threadsafe, run_callback_threadsafe)
2016-08-09 05:21:40 +02:00
import homeassistant.util as util
import homeassistant.util.dt as dt_util
import homeassistant.util.location as location
2016-08-09 05:42:25 +02:00
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
2013-09-30 09:20:27 +02:00
2016-09-20 09:06:33 +02:00
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
pass
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
2016-08-09 05:21:40 +02:00
# Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
# Interval at which we check if the pool is getting busy
MONITOR_POOL_INTERVAL = 30
2014-11-08 22:57:08 +01:00
_LOGGER = logging.getLogger(__name__)
2013-11-11 23:58:57 +01:00
2016-08-09 05:21:40 +02:00
def split_entity_id(entity_id: str) -> List[str]:
"""Split a state entity_id into domain, object_id."""
return entity_id.split(".", 1)
def valid_entity_id(entity_id: str) -> bool:
"""Test if an entity ID is a valid format."""
return ENTITY_ID_PATTERN.match(entity_id) is not None
def callback(func: Callable[..., None]) -> Callable[..., None]:
"""Annotation to mark method as safe to call from within the event loop."""
# pylint: disable=protected-access
func._hass_callback = True
return func
def is_callback(func: Callable[..., Any]) -> bool:
"""Check if function is safe to be called in the event loop."""
return '_hass_callback' in func.__dict__
class CoreState(enum.Enum):
"""Represent the current state of Home Assistant."""
not_running = "NOT_RUNNING"
starting = "STARTING"
running = "RUNNING"
stopping = "STOPPING"
def __str__(self) -> str:
"""Return the event."""
return self.value
class JobPriority(util.OrderedEnum):
"""Provides job priorities for event bus jobs."""
EVENT_CALLBACK = 0
EVENT_SERVICE = 1
EVENT_STATE = 2
EVENT_TIME = 3
EVENT_DEFAULT = 4
@staticmethod
def from_event_type(event_type):
"""Return a priority based on event type."""
if event_type == EVENT_TIME_CHANGED:
return JobPriority.EVENT_TIME
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
return JobPriority.EVENT_DEFAULT
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
# pylint: disable=too-many-instance-attributes
def __init__(self, loop=None):
2015-12-28 06:14:35 +01:00
"""Initialize new Home Assistant object."""
self.loop = loop or asyncio.get_event_loop()
2016-10-02 00:43:33 +02:00
self.executor = ThreadPoolExecutor(max_workers=5)
self.loop.set_default_executor(self.executor)
self.loop.set_exception_handler(self._async_exception_handler)
self.pool = create_worker_pool()
self.bus = EventBus(self)
self.services = ServiceRegistry(self.bus, self.add_job, self.loop)
self.states = StateMachine(self.bus, self.loop)
Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc
2016-07-31 22:24:49 +02:00
self.config = Config() # type: Config
self.state = CoreState.not_running
self.exit_code = None
self._websession = None
@property
def is_running(self) -> bool:
"""Return if Home Assistant is running."""
return self.state in (CoreState.starting, CoreState.running)
@property
def websession(self):
"""Return an aiohttp session to make web requests."""
if self._websession is None:
self._websession = aiohttp.ClientSession(loop=self.loop)
return self._websession
def start(self) -> None:
2015-12-28 06:14:35 +01:00
"""Start home assistant."""
# Register the async start
self.loop.create_task(self.async_start())
# Run forever and catch keyboard interrupt
try:
# Block until stopped
_LOGGER.info("Starting Home Assistant core loop")
self.loop.run_forever()
except KeyboardInterrupt:
self.loop.call_soon(self._async_stop_handler)
self.loop.run_forever()
finally:
self.loop.close()
@asyncio.coroutine
def async_start(self):
"""Finalize startup from inside the event loop.
This method is a coroutine.
"""
_LOGGER.info(
"Starting Home Assistant (%d threads)", self.pool.worker_count)
self.state = CoreState.starting
# Register the restart/stop event
self.services.async_register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, self._async_stop_handler)
self.services.async_register(
DOMAIN, SERVICE_HOMEASSISTANT_RESTART, self._async_restart_handler)
# Setup signal handling
if sys.platform != 'win32':
try:
self.loop.add_signal_handler(
signal.SIGTERM, self._async_stop_handler)
except ValueError:
_LOGGER.warning('Could not bind to SIGTERM.')
try:
self.loop.add_signal_handler(
signal.SIGHUP, self._async_restart_handler)
except ValueError:
_LOGGER.warning('Could not bind to SIGHUP.')
# pylint: disable=protected-access
self.loop._thread_ident = threading.get_ident()
_async_create_timer(self)
_async_monitor_worker_pool(self)
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from self.loop.run_in_executor(None, self.pool.block_till_done)
self.state = CoreState.running
def add_job(self,
target: Callable[..., None],
*args: Any,
priority: JobPriority=JobPriority.EVENT_DEFAULT) -> None:
"""Add job to the worker pool.
target: target to call.
args: parameters for method to call.
"""
self.pool.add_job(priority, (target,) + args)
@callback
def async_add_job(self, target: Callable[..., None], *args: Any):
"""Add a job from within the eventloop.
This method must be run in the event loop.
target: target to call.
args: parameters for method to call.
"""
if is_callback(target):
self.loop.call_soon(target, *args)
elif asyncio.iscoroutinefunction(target):
self.loop.create_task(target(*args))
else:
self.add_job(target, *args)
@callback
def async_run_job(self, target: Callable[..., None], *args: Any):
"""Run a job from within the event loop.
This method must be run in the event loop.
target: target to call.
args: parameters for method to call.
"""
if is_callback(target):
target(*args)
else:
self.async_add_job(target, *args)
def _loop_empty(self):
"""Python 3.4.2 empty loop compatibility function."""
# pylint: disable=protected-access
if sys.version_info < (3, 4, 3):
return len(self.loop._scheduled) == 0 and \
len(self.loop._ready) == 0
else:
return self.loop._current_handle is None and \
len(self.loop._ready) == 0
def block_till_done(self):
"""Block till all pending work is done."""
complete = threading.Event()
@asyncio.coroutine
def sleep_wait():
"""Sleep in thread pool."""
yield from self.loop.run_in_executor(None, time.sleep, 0)
def notify_when_done():
"""Notify event loop when pool done."""
count = 0
while True:
# Wait for the work queue to empty
self.pool.block_till_done()
# Verify the loop is empty
if self._loop_empty():
count += 1
if count == 2:
break
# sleep in the loop executor, this forces execution back into
# the event loop to avoid the block thread from starving the
# async loop
run_coroutine_threadsafe(sleep_wait(), self.loop).result()
complete.set()
threading.Thread(name="BlockThread", target=notify_when_done).start()
complete.wait()
2014-11-23 18:51:16 +01:00
def stop(self) -> None:
2015-12-28 06:14:35 +01:00
"""Stop Home Assistant and shuts down all threads."""
run_coroutine_threadsafe(self.async_stop(), self.loop)
@asyncio.coroutine
def async_stop(self) -> None:
"""Stop Home Assistant and shuts down all threads.
This method is a coroutine.
"""
self.state = CoreState.stopping
self.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
yield from self.loop.run_in_executor(None, self.pool.block_till_done)
yield from self.loop.run_in_executor(None, self.pool.stop)
2016-10-02 00:43:33 +02:00
self.executor.shutdown()
if self._websession is not None:
yield from self._websession.close()
self.state = CoreState.not_running
self.loop.stop()
2014-11-23 18:51:16 +01:00
# pylint: disable=no-self-use
@callback
def _async_exception_handler(self, loop, context):
"""Handle all exception inside the core loop."""
message = context.get('message')
if message:
_LOGGER.warning(
"Error inside async loop: %s",
message
)
# for debug modus
exception = context.get('exception')
if exception is not None:
exc_info = (type(exception), exception, exception.__traceback__)
_LOGGER.debug(
"Exception inside async loop: ",
exc_info=exc_info
)
@callback
def _async_stop_handler(self, *args):
"""Stop Home Assistant."""
self.exit_code = 0
self.async_add_job(self.async_stop)
@callback
def _async_restart_handler(self, *args):
"""Restart Home Assistant."""
self.exit_code = RESTART_EXIT_CODE
self.async_add_job(self.async_stop)
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):
"""Create a dict representation of this Event.
Async friendly.
"""
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, hass: HomeAssistant) -> None:
2015-12-28 06:14:35 +01:00
"""Initialize a new event bus."""
self._listeners = {}
self._hass = hass
@callback
def async_listeners(self):
"""Dict with events and the number of listeners.
This method must be run in the event loop.
"""
return {key: len(self._listeners[key])
for key in self._listeners}
@property
def listeners(self):
2015-12-28 06:14:35 +01:00
"""Dict with events and the number of listeners."""
return run_callback_threadsafe(
self._hass.loop, self.async_listeners
).result()
2014-01-30 07:48:35 +01:00
def fire(self, event_type: str, event_data=None, origin=EventOrigin.local):
2015-12-28 06:14:35 +01:00
"""Fire an event."""
self._hass.loop.call_soon_threadsafe(self.async_fire, event_type,
event_data, origin)
2013-09-30 09:20:27 +02:00
@callback
def async_fire(self, event_type: str, event_data=None,
origin=EventOrigin.local, wait=False):
"""Fire an event.
This method must be run in the event loop.
"""
if event_type != EVENT_HOMEASSISTANT_STOP and \
self._hass.state == CoreState.stopping:
raise HomeAssistantError('Home Assistant is shutting down.')
# 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, [])
event = Event(event_type, event_data, origin)
if event_type != EVENT_TIME_CHANGED:
_LOGGER.info("Bus:Handling %s", event)
if not listeners:
return
for func in listeners:
self._hass.async_add_job(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.
"""
async_remove_listener = run_callback_threadsafe(
self._hass.loop, self.async_listen, event_type, listener).result()
2016-08-26 08:25:35 +02:00
def remove_listener():
"""Remove the listener."""
run_callback_threadsafe(
self._hass.loop, async_remove_listener).result()
2016-08-26 08:25:35 +02:00
return remove_listener
@callback
def async_listen(self, event_type, listener):
"""Listen for all events or events of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
as event_type.
This method must be run in the event loop.
"""
if event_type in self._listeners:
self._listeners[event_type].append(listener)
else:
self._listeners[event_type] = [listener]
def remove_listener():
"""Remove the listener."""
self._async_remove_listener(event_type, listener)
return remove_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.
2016-09-07 15:59:59 +02:00
Returns function to unsubscribe the listener.
2014-11-29 08:19:59 +01:00
"""
async_remove_listener = run_callback_threadsafe(
self._hass.loop, self.async_listen_once, event_type, listener,
).result()
2014-11-29 08:19:59 +01:00
def remove_listener():
"""Remove the listener."""
run_callback_threadsafe(
self._hass.loop, async_remove_listener).result()
2014-11-29 08:19:59 +01:00
2016-09-07 15:59:59 +02:00
return remove_listener
@callback
def async_listen_once(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.
Returns registered listener that can be used with remove_listener.
This method must be run in the event loop.
"""
@callback
def onetime_listener(event):
"""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 loop might have async_fire queued multiple
# times, its possible this listener may already be lined up
# multiple times as well.
# This will make sure the second time it does nothing.
setattr(onetime_listener, 'run', True)
self._async_remove_listener(event_type, onetime_listener)
self._hass.async_run_job(listener, event)
return self.async_listen(event_type, onetime_listener)
2016-09-07 15:59:59 +02:00
@callback
def _async_remove_listener(self, event_type, listener):
"""Remove a listener of a specific event_type.
This method must be run in the event loop.
"""
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)
except (KeyError, ValueError):
# KeyError is key event_type listener did not exist
# ValueError if listener did not exist within event_type
_LOGGER.warning('Unable to remove unknown listener %s',
listener)
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.
Async friendly.
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.
Async friendly.
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, loop):
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._loop = loop
2013-09-30 09:20:27 +02:00
2014-11-29 08:19:59 +01:00
def entity_ids(self, domain_filter=None):
"""List of entity ids that are being tracked."""
future = run_callback_threadsafe(
self._loop, self.async_entity_ids, domain_filter
)
return future.result()
@callback
def async_entity_ids(self, domain_filter=None):
"""List of entity ids that are being tracked.
This method must be run in the event loop.
"""
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()
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."""
return run_callback_threadsafe(self._loop, self.async_all).result()
@callback
def async_all(self):
"""Create a list of all states.
This method must be run in the event loop.
"""
return list(self._states.values())
def get(self, entity_id):
"""Retrieve state of entity_id or None if not found.
Async friendly.
"""
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):
"""Test if entity exists and is specified state.
Async friendly.
"""
state_obj = self.get(entity_id)
2015-02-06 09:17:30 +01:00
return state_obj and state_obj.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.
Async friendly.
"""
state_obj = self.get(entity_id)
return state_obj and state_obj.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.
"""
return run_callback_threadsafe(
self._loop, self.async_remove, entity_id).result()
@callback
def async_remove(self, entity_id):
"""Remove the state of an entity.
Returns boolean to indicate if an entity was removed.
This method must be run in the event loop.
"""
2015-02-06 09:17:30 +01:00
entity_id = entity_id.lower()
old_state = self._states.pop(entity_id, None)
2016-02-14 07:57:40 +01:00
if old_state is None:
return False
2016-02-14 07:57:40 +01:00
event_data = {
'entity_id': entity_id,
'old_state': old_state,
'new_state': None,
}
2016-02-14 07:57:40 +01:00
self._bus.async_fire(EVENT_STATE_CHANGED, event_data)
2016-02-14 07:57:40 +01:00
return True
def set(self, entity_id, new_state, attributes=None, force_update=False):
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.
"""
run_callback_threadsafe(
self._loop,
self.async_set, entity_id, new_state, attributes, force_update,
).result()
@callback
def async_set(self, entity_id, new_state, attributes=None,
force_update=False):
"""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.
This method must be run in the event loop.
"""
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
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 and
not force_update)
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
event_data = {
'entity_id': entity_id,
'old_state': old_state,
'new_state': state,
}
self._bus.async_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',
'is_callback', 'is_coroutinefunction']
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
self.is_callback = is_callback(func)
self.is_coroutinefunction = asyncio.iscoroutinefunction(func)
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,
}
# 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.lower()
self.service = service.lower()
self.data = MappingProxyType(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, add_job, loop):
2015-12-28 06:14:35 +01:00
"""Initialize a service registry."""
self._services = {}
self._add_job = add_job
self._bus = bus
self._loop = loop
self._cur_id = 0
self._async_unsub_call_event = None
@property
def services(self):
2015-12-28 06:14:35 +01:00
"""Dict with per domain a list of available services."""
return run_callback_threadsafe(
self._loop, self.async_services,
).result()
@callback
def async_services(self):
"""Dict with per domain a list of available services.
This method must be run in the event loop.
"""
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):
"""Test if specified service exists.
Async friendly.
"""
return service.lower() in self._services.get(domain.lower(), [])
# 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
"""
run_callback_threadsafe(
self._loop,
self.async_register, domain, service, service_func, description,
schema
).result()
@callback
def async_register(self, domain, service, service_func, description=None,
schema=None):
"""
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.
This method must be run in the event loop.
"""
domain = domain.lower()
service = service.lower()
2015-09-27 08:17:04 +02:00
description = description or {}
service_obj = Service(service_func, description.get('description'),
description.get('fields', {}), schema)
if domain in self._services:
self._services[domain][service] = service_obj
else:
self._services[domain] = {service: service_obj}
if self._async_unsub_call_event is None:
self._async_unsub_call_event = self._bus.async_listen(
EVENT_CALL_SERVICE, self._event_to_service_call)
self._bus.async_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.
"""
return run_coroutine_threadsafe(
self.async_call(domain, service, service_data, blocking),
self._loop
).result()
@asyncio.coroutine
def async_call(self, domain, service, service_data=None, blocking=False):
"""
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.
This method is a coroutine.
"""
call_id = self._generate_unique_id()
event_data = {
ATTR_DOMAIN: domain.lower(),
ATTR_SERVICE: service.lower(),
ATTR_SERVICE_DATA: service_data,
ATTR_SERVICE_CALL_ID: call_id,
}
if blocking:
fut = asyncio.Future(loop=self._loop)
@callback
def service_executed(event):
2015-12-28 06:14:35 +01:00
"""Callback method that is called when service is executed."""
if event.data[ATTR_SERVICE_CALL_ID] == call_id:
fut.set_result(True)
unsub = self._bus.async_listen(EVENT_SERVICE_EXECUTED,
service_executed)
self._bus.async_fire(EVENT_CALL_SERVICE, event_data)
if blocking:
done, _ = yield from asyncio.wait([fut], loop=self._loop,
timeout=SERVICE_CALL_LIMIT)
success = bool(done)
2016-09-07 15:59:59 +02:00
unsub()
2015-08-04 18:13:55 +02:00
return success
@asyncio.coroutine
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) or {}
domain = event.data.get(ATTR_DOMAIN).lower()
service = event.data.get(ATTR_SERVICE).lower()
call_id = event.data.get(ATTR_SERVICE_CALL_ID)
if not self.has_service(domain, service):
if event.origin == EventOrigin.local:
_LOGGER.warning('Unable to find service %s/%s',
domain, service)
return
service_handler = self._services[domain][service]
def fire_service_executed():
"""Fire service executed event."""
if not call_id:
return
data = {ATTR_SERVICE_CALL_ID: call_id}
if (service_handler.is_coroutinefunction or
service_handler.is_callback):
self._bus.async_fire(EVENT_SERVICE_EXECUTED, data)
else:
self._bus.fire(EVENT_SERVICE_EXECUTED, data)
try:
if service_handler.schema:
service_data = service_handler.schema(service_data)
except vol.Invalid as ex:
_LOGGER.error('Invalid service data for %s.%s: %s',
domain, service, humanize_error(service_data, ex))
fire_service_executed()
return
service_call = ServiceCall(domain, service, service_data, call_id)
if service_handler.is_callback:
service_handler.func(service_call)
fire_service_executed()
elif service_handler.is_coroutinefunction:
yield from service_handler.func(service_call)
fire_service_executed()
else:
def execute_service():
"""Execute a service and fires a SERVICE_EXECUTED event."""
service_handler.func(service_call)
fire_service_executed()
self._add_job(execute_service, priority=JobPriority.EVENT_SERVICE)
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 # type: Optional[float]
self.longitude = None # type: Optional[float]
self.elevation = None # type: Optional[int]
self.location_name = None # type: Optional[str]
self.time_zone = None # type: Optional[str]
2016-08-09 05:42:25 +02:00
self.units = METRIC_SYSTEM # type: UnitSystem
# If True, pip install is skipped for requirements on startup
self.skip_pip = False # type: bool
# List of loaded components
self.components = []
# Remote.API object pointing at local API
self.api = None
# Directory that holds the configuration
2016-08-09 05:21:40 +02:00
self.config_dir = None
Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc
2016-07-31 22:24:49 +02:00
def distance(self: object, lat: float, lon: float) -> float:
"""Calculate distance from Home Assistant.
Async friendly.
"""
Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc
2016-07-31 22:24:49 +02:00
return self.units.length(
location.distance(self.latitude, self.longitude, lat, lon), 'm')
2015-09-20 18:35:03 +02:00
def path(self, *path):
"""Generate path to the file within the config dir.
Async friendly.
"""
2016-08-09 05:21:40 +02:00
if self.config_dir is None:
raise HomeAssistantError("config_dir is not set")
return os.path.join(self.config_dir, *path)
2015-05-02 03:24:32 +02:00
def as_dict(self):
"""Create a dict representation of this dict.
Async friendly.
"""
time_zone = self.time_zone or dt_util.UTC
2015-05-02 03:24:32 +02:00
return {
'latitude': self.latitude,
'longitude': self.longitude,
Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc
2016-07-31 22:24:49 +02:00
'unit_system': self.units.as_dict(),
2015-05-02 03:24:32 +02:00
'location_name': self.location_name,
'time_zone': time_zone.zone,
2015-05-02 03:24:32 +02:00
'components': self.components,
'config_dir': self.config_dir,
2015-10-26 05:00:22 +01:00
'version': __version__
2015-05-02 03:24:32 +02:00
}
def _async_create_timer(hass, interval=TIMER_INTERVAL):
2015-12-28 06:14:35 +01:00
"""Create a timer that will start on HOMEASSISTANT_START."""
stop_event = asyncio.Event(loop=hass.loop)
# Setting the Event inside the loop by marking it as a coroutine
@callback
def stop_timer(event):
"""Stop the timer."""
stop_event.set()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer)
@asyncio.coroutine
def timer(interval, stop_event):
"""Create an async timer."""
_LOGGER.info("Timer:starting")
last_fired_on_second = -1
calc_now = dt_util.utcnow
while not stop_event.is_set():
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
yield from asyncio.sleep(slp_seconds, loop=hass.loop)
now = calc_now()
last_fired_on_second = now.second
# Event might have been set while sleeping
if not stop_event.is_set():
try:
# Schedule the bus event
hass.loop.call_soon(
hass.bus.async_fire,
EVENT_TIME_CHANGED,
{ATTR_NOW: now}
)
except HomeAssistantError:
# HA raises error if firing event after it has shut down
break
@asyncio.coroutine
def start_timer(event):
"""Start our async timer."""
hass.loop.create_task(timer(interval, stop_event))
hass.bus.async_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, *args = job
func(*args)
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")
return util.ThreadPool(job_handler, worker_count)
def _async_monitor_worker_pool(hass):
"""Create a monitor for the thread pool to check if pool is misbehaving."""
busy_threshold = hass.pool.worker_count * 3
handle = None
def schedule():
"""Schedule the monitor."""
nonlocal handle
handle = hass.loop.call_later(MONITOR_POOL_INTERVAL,
check_pool_threshold)
def check_pool_threshold():
"""Check pool size."""
nonlocal busy_threshold
pending_jobs = hass.pool.queue_size
if pending_jobs < busy_threshold:
schedule()
return
_LOGGER.warning(
"WorkerPool:All %d threads are busy and %d jobs pending",
hass.pool.worker_count, pending_jobs)
for start, job in hass.pool.current_jobs:
_LOGGER.warning("WorkerPool:Current job started at %s: %s",
2016-04-16 09:55:35 +02:00
dt_util.as_local(start).isoformat(), job)
busy_threshold *= 2
schedule()
schedule()
@callback
def stop_monitor(event):
"""Stop the monitor."""
handle.cancel()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor)