mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 02:07:09 +00:00
Merge pull request #121 from balloob/dev
Update master with latest changes
This commit is contained in:
commit
00664ac601
42
.coveragerc
42
.coveragerc
@ -2,6 +2,8 @@
|
|||||||
source = homeassistant
|
source = homeassistant
|
||||||
|
|
||||||
omit =
|
omit =
|
||||||
|
homeassistant/__main__.py
|
||||||
|
|
||||||
homeassistant/external/*
|
homeassistant/external/*
|
||||||
|
|
||||||
# omit pieces of code that rely on external devices being present
|
# omit pieces of code that rely on external devices being present
|
||||||
@ -11,29 +13,37 @@ omit =
|
|||||||
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/keyboard.py
|
homeassistant/components/browser.py
|
||||||
homeassistant/components/switch/wemo.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
homeassistant/components/thermostat/nest.py
|
|
||||||
homeassistant/components/light/hue.py
|
|
||||||
homeassistant/components/sensor/systemmonitor.py
|
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
|
||||||
homeassistant/components/notify/pushbullet.py
|
|
||||||
homeassistant/components/notify/pushover.py
|
|
||||||
homeassistant/components/media_player/cast.py
|
|
||||||
homeassistant/components/device_tracker/luci.py
|
homeassistant/components/device_tracker/luci.py
|
||||||
homeassistant/components/device_tracker/tomato.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/ddwrt.py
|
homeassistant/components/device_tracker/tomato.py
|
||||||
|
homeassistant/components/keyboard.py
|
||||||
|
homeassistant/components/light/hue.py
|
||||||
|
homeassistant/components/media_player/cast.py
|
||||||
|
homeassistant/components/notify/instapush.py
|
||||||
|
homeassistant/components/notify/nma.py
|
||||||
|
homeassistant/components/notify/pushbullet.py
|
||||||
|
homeassistant/components/notify/pushover.py
|
||||||
|
homeassistant/components/notify/xmpp.py
|
||||||
|
homeassistant/components/sensor/mysensors.py
|
||||||
|
homeassistant/components/sensor/openweathermap.py
|
||||||
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
|
homeassistant/components/sensor/systemmonitor.py
|
||||||
|
homeassistant/components/sensor/time_date.py
|
||||||
homeassistant/components/sensor/transmission.py
|
homeassistant/components/sensor/transmission.py
|
||||||
|
homeassistant/components/switch/wemo.py
|
||||||
homeassistant/components/isy994.py
|
homeassistant/components/thermostat/nest.py
|
||||||
homeassistant/components/light/isy994.py
|
|
||||||
homeassistant/components/switch/isy994.py
|
|
||||||
homeassistant/components/sensor/isy994.py
|
|
||||||
|
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -19,3 +19,6 @@
|
|||||||
[submodule "homeassistant/external/nzbclients"]
|
[submodule "homeassistant/external/nzbclients"]
|
||||||
path = homeassistant/external/nzbclients
|
path = homeassistant/external/nzbclients
|
||||||
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
|
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
|
||||||
|
[submodule "homeassistant/external/pymysensors"]
|
||||||
|
path = homeassistant/external/pymysensors
|
||||||
|
url = https://github.com/theolind/pymysensors
|
||||||
|
@ -28,7 +28,8 @@ wink:
|
|||||||
access_token: 'YOUR_TOKEN'
|
access_token: 'YOUR_TOKEN'
|
||||||
|
|
||||||
device_tracker:
|
device_tracker:
|
||||||
# The following types are available: netgear, tomato, luci, nmap_tracker
|
# The following types are available: ddwrt, netgear, tomato, luci,
|
||||||
|
# and nmap_tracker
|
||||||
platform: netgear
|
platform: netgear
|
||||||
host: 192.168.1.1
|
host: 192.168.1.1
|
||||||
username: admin
|
username: admin
|
||||||
@ -65,7 +66,7 @@ device_sun_light_trigger:
|
|||||||
# Optional: disable lights being turned off when everybody leaves the house
|
# Optional: disable lights being turned off when everybody leaves the house
|
||||||
# disable_turn_off: 1
|
# disable_turn_off: 1
|
||||||
|
|
||||||
# A comma seperated list of states that have to be tracked as a single group
|
# A comma separated list of states that have to be tracked as a single group
|
||||||
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
|
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
|
||||||
group:
|
group:
|
||||||
living_room:
|
living_room:
|
||||||
|
@ -22,7 +22,7 @@ DOMAIN = "example"
|
|||||||
|
|
||||||
# List of component names (string) your component depends upon
|
# List of component names (string) your component depends upon
|
||||||
# We depend on group because group will be loaded after all the components that
|
# We depend on group because group will be loaded after all the components that
|
||||||
# initalize devices have been setup.
|
# initialize devices have been setup.
|
||||||
DEPENDENCIES = ['group']
|
DEPENDENCIES = ['group']
|
||||||
|
|
||||||
# Configuration key for the entity id we are targetting
|
# Configuration key for the entity id we are targetting
|
||||||
@ -115,5 +115,5 @@ def setup(hass, config):
|
|||||||
# Register our service with HASS.
|
# Register our service with HASS.
|
||||||
hass.services.register(DOMAIN, SERVICE_FLASH, flash_service)
|
hass.services.register(DOMAIN, SERVICE_FLASH, flash_service)
|
||||||
|
|
||||||
# Tells the bootstrapper that the component was succesfully initialized
|
# Tells the bootstrapper that the component was successfully initialized
|
||||||
return True
|
return True
|
||||||
|
@ -12,11 +12,8 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import enum
|
import enum
|
||||||
import re
|
import re
|
||||||
import datetime as dt
|
|
||||||
import functools as ft
|
import functools as ft
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||||
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||||
@ -24,6 +21,7 @@ from homeassistant.const import (
|
|||||||
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
|
|
||||||
DOMAIN = "homeassistant"
|
DOMAIN = "homeassistant"
|
||||||
|
|
||||||
@ -109,7 +107,20 @@ class HomeAssistant(object):
|
|||||||
|
|
||||||
def track_point_in_time(self, action, point_in_time):
|
def track_point_in_time(self, action, point_in_time):
|
||||||
"""
|
"""
|
||||||
Adds a listener that fires once at or after a spefic 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)
|
@ft.wraps(action)
|
||||||
@ -135,11 +146,19 @@ class HomeAssistant(object):
|
|||||||
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
||||||
return 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
|
# pylint: disable=too-many-arguments
|
||||||
def track_time_change(self, action,
|
def track_time_change(self, action,
|
||||||
year=None, month=None, day=None,
|
year=None, month=None, day=None,
|
||||||
hour=None, minute=None, second=None):
|
hour=None, minute=None, second=None, utc=False):
|
||||||
""" Adds a listener that will fire if time matches a pattern. """
|
""" 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
|
# We do not have to wrap the function with time pattern matching logic
|
||||||
# if no pattern given
|
# if no pattern given
|
||||||
@ -155,6 +174,9 @@ class HomeAssistant(object):
|
|||||||
""" Listens for matching time_changed events. """
|
""" Listens for matching time_changed events. """
|
||||||
now = event.data[ATTR_NOW]
|
now = event.data[ATTR_NOW]
|
||||||
|
|
||||||
|
if not utc:
|
||||||
|
now = date_util.as_local(now)
|
||||||
|
|
||||||
mat = _matcher
|
mat = _matcher
|
||||||
|
|
||||||
if mat(now.year, year) and \
|
if mat(now.year, year) and \
|
||||||
@ -305,7 +327,7 @@ def create_worker_pool():
|
|||||||
|
|
||||||
for start, job in current_jobs:
|
for start, job in current_jobs:
|
||||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
||||||
util.datetime_to_str(start), job)
|
date_util.datetime_to_local_str(start), job)
|
||||||
|
|
||||||
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
||||||
|
|
||||||
@ -333,7 +355,7 @@ class Event(object):
|
|||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.time_fired = util.strip_microseconds(
|
self.time_fired = util.strip_microseconds(
|
||||||
time_fired or dt.datetime.now())
|
time_fired or date_util.utcnow())
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Returns a dict representation of this Event. """
|
""" Returns a dict representation of this Event. """
|
||||||
@ -341,7 +363,7 @@ class Event(object):
|
|||||||
'event_type': self.event_type,
|
'event_type': self.event_type,
|
||||||
'data': dict(self.data),
|
'data': dict(self.data),
|
||||||
'origin': str(self.origin),
|
'origin': str(self.origin),
|
||||||
'time_fired': util.datetime_to_str(self.time_fired),
|
'time_fired': date_util.datetime_to_str(self.time_fired),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -354,6 +376,13 @@ class Event(object):
|
|||||||
return "<Event {}[{}]>".format(self.event_type,
|
return "<Event {}[{}]>".format(self.event_type,
|
||||||
str(self.origin)[0])
|
str(self.origin)[0])
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class EventBus(object):
|
class EventBus(object):
|
||||||
""" Class that allows different components to communicate via services
|
""" Class that allows different components to communicate via services
|
||||||
@ -376,6 +405,9 @@ class EventBus(object):
|
|||||||
|
|
||||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
||||||
""" Fire an event. """
|
""" Fire an event. """
|
||||||
|
if not self._pool.running:
|
||||||
|
raise HomeAssistantError('Home Assistant has shut down.')
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# Copy the list of the current listeners because some listeners
|
# Copy the list of the current listeners because some listeners
|
||||||
# remove themselves as a listener while being executed which
|
# remove themselves as a listener while being executed which
|
||||||
@ -474,13 +506,14 @@ class State(object):
|
|||||||
self.entity_id = entity_id.lower()
|
self.entity_id = entity_id.lower()
|
||||||
self.state = state
|
self.state = state
|
||||||
self.attributes = attributes or {}
|
self.attributes = attributes or {}
|
||||||
self.last_updated = last_updated or dt.datetime.now()
|
self.last_updated = date_util.strip_microseconds(
|
||||||
|
last_updated or date_util.utcnow())
|
||||||
|
|
||||||
# Strip microsecond from last_changed else we cannot guarantee
|
# Strip microsecond from last_changed else we cannot guarantee
|
||||||
# state == State.from_dict(state.as_dict())
|
# state == State.from_dict(state.as_dict())
|
||||||
# This behavior occurs because to_dict uses datetime_to_str
|
# This behavior occurs because to_dict uses datetime_to_str
|
||||||
# which does not preserve microseconds
|
# which does not preserve microseconds
|
||||||
self.last_changed = util.strip_microseconds(
|
self.last_changed = date_util.strip_microseconds(
|
||||||
last_changed or self.last_updated)
|
last_changed or self.last_updated)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -512,8 +545,8 @@ class State(object):
|
|||||||
return {'entity_id': self.entity_id,
|
return {'entity_id': self.entity_id,
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
'attributes': self.attributes,
|
'attributes': self.attributes,
|
||||||
'last_changed': util.datetime_to_str(self.last_changed),
|
'last_changed': date_util.datetime_to_str(self.last_changed),
|
||||||
'last_updated': util.datetime_to_str(self.last_updated)}
|
'last_updated': date_util.datetime_to_str(self.last_updated)}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, json_dict):
|
def from_dict(cls, json_dict):
|
||||||
@ -528,12 +561,12 @@ class State(object):
|
|||||||
last_changed = json_dict.get('last_changed')
|
last_changed = json_dict.get('last_changed')
|
||||||
|
|
||||||
if last_changed:
|
if last_changed:
|
||||||
last_changed = util.str_to_datetime(last_changed)
|
last_changed = date_util.str_to_datetime(last_changed)
|
||||||
|
|
||||||
last_updated = json_dict.get('last_updated')
|
last_updated = json_dict.get('last_updated')
|
||||||
|
|
||||||
if last_updated:
|
if last_updated:
|
||||||
last_updated = util.str_to_datetime(last_updated)
|
last_updated = date_util.str_to_datetime(last_updated)
|
||||||
|
|
||||||
return cls(json_dict['entity_id'], json_dict['state'],
|
return cls(json_dict['entity_id'], json_dict['state'],
|
||||||
json_dict.get('attributes'), last_changed, last_updated)
|
json_dict.get('attributes'), last_changed, last_updated)
|
||||||
@ -550,7 +583,7 @@ class State(object):
|
|||||||
|
|
||||||
return "<state {}={}{} @ {}>".format(
|
return "<state {}={}{} @ {}>".format(
|
||||||
self.entity_id, self.state, attr,
|
self.entity_id, self.state, attr,
|
||||||
util.datetime_to_str(self.last_changed))
|
date_util.datetime_to_local_str(self.last_changed))
|
||||||
|
|
||||||
|
|
||||||
class StateMachine(object):
|
class StateMachine(object):
|
||||||
@ -587,7 +620,7 @@ class StateMachine(object):
|
|||||||
"""
|
"""
|
||||||
Returns all states that have been changed since point_in_time.
|
Returns all states that have been changed since point_in_time.
|
||||||
"""
|
"""
|
||||||
point_in_time = util.strip_microseconds(point_in_time)
|
point_in_time = date_util.strip_microseconds(point_in_time)
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return [state for state in self._states.values()
|
return [state for state in self._states.values()
|
||||||
@ -849,7 +882,7 @@ class Timer(threading.Thread):
|
|||||||
|
|
||||||
last_fired_on_second = -1
|
last_fired_on_second = -1
|
||||||
|
|
||||||
calc_now = dt.datetime.now
|
calc_now = date_util.utcnow
|
||||||
interval = self.interval
|
interval = self.interval
|
||||||
|
|
||||||
while not self._stop_event.isSet():
|
while not self._stop_event.isSet():
|
||||||
@ -875,7 +908,13 @@ class Timer(threading.Thread):
|
|||||||
|
|
||||||
last_fired_on_second = now.second
|
last_fired_on_second = now.second
|
||||||
|
|
||||||
self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
# 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):
|
||||||
@ -898,44 +937,9 @@ class Config(object):
|
|||||||
# Directory that holds the configuration
|
# Directory that holds the configuration
|
||||||
self.config_dir = os.path.join(os.getcwd(), 'config')
|
self.config_dir = os.path.join(os.getcwd(), 'config')
|
||||||
|
|
||||||
def auto_detect(self):
|
def path(self, *path):
|
||||||
""" Will attempt to detect config of Home Assistant. """
|
|
||||||
# Only detect if location or temp unit missing
|
|
||||||
if None not in (self.latitude, self.longitude, self.temperature_unit):
|
|
||||||
return
|
|
||||||
|
|
||||||
_LOGGER.info('Auto detecting location and temperature unit')
|
|
||||||
|
|
||||||
try:
|
|
||||||
info = requests.get(
|
|
||||||
'https://freegeoip.net/json/', timeout=5).json()
|
|
||||||
except requests.RequestException:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.latitude is None and self.longitude is None:
|
|
||||||
self.latitude = info['latitude']
|
|
||||||
self.longitude = info['longitude']
|
|
||||||
|
|
||||||
if self.temperature_unit is None:
|
|
||||||
# From Wikipedia:
|
|
||||||
# Fahrenheit is used in the Bahamas, Belize, the Cayman Islands,
|
|
||||||
# Palau, and the United States and associated territories of
|
|
||||||
# American Samoa and the U.S. Virgin Islands
|
|
||||||
if info['country_code'] in ('BS', 'BZ', 'KY', 'PW',
|
|
||||||
'US', 'AS', 'VI'):
|
|
||||||
self.temperature_unit = TEMP_FAHRENHEIT
|
|
||||||
else:
|
|
||||||
self.temperature_unit = TEMP_CELCIUS
|
|
||||||
|
|
||||||
if self.location_name is None:
|
|
||||||
self.location_name = info['city']
|
|
||||||
|
|
||||||
if self.time_zone is None:
|
|
||||||
self.time_zone = info['time_zone']
|
|
||||||
|
|
||||||
def path(self, path):
|
|
||||||
""" Returns path to the file within the config dir. """
|
""" Returns path to the file within the config dir. """
|
||||||
return os.path.join(self.config_dir, path)
|
return os.path.join(self.config_dir, *path)
|
||||||
|
|
||||||
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. """
|
||||||
@ -955,6 +959,17 @@ class Config(object):
|
|||||||
# Could not convert value to float
|
# Could not convert value to float
|
||||||
return value, unit
|
return value, unit
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
""" Converts config to a dictionary. """
|
||||||
|
return {
|
||||||
|
'latitude': self.latitude,
|
||||||
|
'longitude': self.longitude,
|
||||||
|
'temperature_unit': self.temperature_unit,
|
||||||
|
'location_name': self.location_name,
|
||||||
|
'time_zone': self.time_zone.zone,
|
||||||
|
'components': self.components,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantError(Exception):
|
class HomeAssistantError(Exception):
|
||||||
""" General Home Assistant exception occured. """
|
""" General Home Assistant exception occured. """
|
||||||
|
@ -7,6 +7,14 @@ import argparse
|
|||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
|
# Home Assistant dependencies, mapped module -> package name
|
||||||
|
DEPENDENCIES = {
|
||||||
|
'requests': 'requests',
|
||||||
|
'yaml': 'pyyaml',
|
||||||
|
'pytz': 'pytz',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_python():
|
def validate_python():
|
||||||
""" Validate we're running the right Python version. """
|
""" Validate we're running the right Python version. """
|
||||||
major, minor = sys.version_info[:2]
|
major, minor = sys.version_info[:2]
|
||||||
@ -20,13 +28,13 @@ def validate_dependencies():
|
|||||||
""" Validate all dependencies that HA uses. """
|
""" Validate all dependencies that HA uses. """
|
||||||
import_fail = False
|
import_fail = False
|
||||||
|
|
||||||
for module in ['requests']:
|
for module, name in DEPENDENCIES.items():
|
||||||
try:
|
try:
|
||||||
importlib.import_module(module)
|
importlib.import_module(module)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import_fail = True
|
import_fail = True
|
||||||
print(
|
print(
|
||||||
'Fatal Error: Unable to find dependency {}'.format(module))
|
'Fatal Error: Unable to find dependency {}'.format(name))
|
||||||
|
|
||||||
if import_fail:
|
if import_fail:
|
||||||
print(("Install dependencies by running: "
|
print(("Install dependencies by running: "
|
||||||
@ -72,24 +80,13 @@ def ensure_config_path(config_dir):
|
|||||||
'directory {} ').format(config_dir))
|
'directory {} ').format(config_dir))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Try to use yaml configuration first
|
import homeassistant.config as config_util
|
||||||
config_path = os.path.join(config_dir, 'configuration.yaml')
|
|
||||||
if not os.path.isfile(config_path):
|
|
||||||
config_path = os.path.join(config_dir, 'home-assistant.conf')
|
|
||||||
|
|
||||||
# Ensure a config file exists to make first time usage easier
|
config_path = config_util.ensure_config_exists(config_dir)
|
||||||
if not os.path.isfile(config_path):
|
|
||||||
config_path = os.path.join(config_dir, 'configuration.yaml')
|
if config_path is None:
|
||||||
try:
|
print('Error getting configuration path')
|
||||||
with open(config_path, 'w') as conf:
|
sys.exit()
|
||||||
conf.write("frontend:\n\n")
|
|
||||||
conf.write("discovery:\n\n")
|
|
||||||
conf.write("history:\n\n")
|
|
||||||
conf.write("logbook:\n\n")
|
|
||||||
except IOError:
|
|
||||||
print(('Fatal Error: No configuration file found and unable '
|
|
||||||
'to write a default one to {}').format(config_path))
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
|
@ -10,25 +10,27 @@ start by calling homeassistant.start_home_assistant(bus)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import configparser
|
|
||||||
import yaml
|
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import homeassistant
|
import homeassistant
|
||||||
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
|
import homeassistant.config as config_util
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
import homeassistant.components.group as group
|
import homeassistant.components.group as group
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
|
EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
|
||||||
CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_VISIBILITY,
|
CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_CUSTOMIZE,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_COMPONENT = "component"
|
ATTR_COMPONENT = 'component'
|
||||||
|
|
||||||
|
PLATFORM_FORMAT = '{}.{}'
|
||||||
|
|
||||||
|
|
||||||
def setup_component(hass, domain, config=None):
|
def setup_component(hass, domain, config=None):
|
||||||
@ -67,7 +69,7 @@ def _setup_component(hass, domain, config):
|
|||||||
|
|
||||||
if missing_deps:
|
if missing_deps:
|
||||||
_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
|
||||||
@ -87,14 +89,42 @@ def _setup_component(hass, domain, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("component %s failed to initialize", domain)
|
_LOGGER.error('component %s failed to initialize', domain)
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error during setup of component %s", domain)
|
_LOGGER.exception('Error during setup of component %s', domain)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_setup_platform(hass, config, domain, platform_name):
|
||||||
|
""" Loads a platform and makes sure dependencies are setup. """
|
||||||
|
_ensure_loader_prepared(hass)
|
||||||
|
|
||||||
|
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
|
||||||
|
|
||||||
|
platform = loader.get_component(platform_path)
|
||||||
|
|
||||||
|
# Not found
|
||||||
|
if platform is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Already loaded or no dependencies
|
||||||
|
elif (platform_path in hass.config.components or
|
||||||
|
not hasattr(platform, 'DEPENDENCIES')):
|
||||||
|
return platform
|
||||||
|
|
||||||
|
# Load dependencies
|
||||||
|
for component in platform.DEPENDENCIES:
|
||||||
|
if not setup_component(hass, component, config):
|
||||||
|
_LOGGER.error(
|
||||||
|
'Unable to prepare setup for platform %s because dependency '
|
||||||
|
'%s could not be initialized', platform_path, component)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return platform
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-statements
|
# pylint: disable=too-many-branches, too-many-statements
|
||||||
def from_config_dict(config, hass=None):
|
def from_config_dict(config, hass=None):
|
||||||
"""
|
"""
|
||||||
@ -122,12 +152,12 @@ def from_config_dict(config, hass=None):
|
|||||||
if ' ' not in key and key != homeassistant.DOMAIN)
|
if ' ' not in key and key != homeassistant.DOMAIN)
|
||||||
|
|
||||||
if not core_components.setup(hass, config):
|
if not core_components.setup(hass, config):
|
||||||
_LOGGER.error("Home Assistant core failed to initialize. "
|
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||||
"Further initialization aborted.")
|
'Further initialization aborted.')
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
_LOGGER.info("Home Assistant core initialized")
|
_LOGGER.info('Home Assistant core initialized')
|
||||||
|
|
||||||
# Setup the components
|
# Setup the components
|
||||||
for domain in loader.load_order_components(components):
|
for domain in loader.load_order_components(components):
|
||||||
@ -148,26 +178,7 @@ def from_config_file(config_path, hass=None):
|
|||||||
# Set config dir to directory holding config file
|
# Set config dir to directory holding config file
|
||||||
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
|
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||||
|
|
||||||
config_dict = {}
|
config_dict = config_util.load_config_file(config_path)
|
||||||
# check config file type
|
|
||||||
if os.path.splitext(config_path)[1] == '.yaml':
|
|
||||||
# Read yaml
|
|
||||||
config_dict = yaml.load(io.open(config_path, 'r'))
|
|
||||||
|
|
||||||
# If YAML file was empty
|
|
||||||
if config_dict is None:
|
|
||||||
config_dict = {}
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Read config
|
|
||||||
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 from_config_dict(config_dict, hass)
|
return from_config_dict(config_dict, hass)
|
||||||
|
|
||||||
@ -177,7 +188,7 @@ def enable_logging(hass):
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Log errors to a file if we have write access to file or config dir
|
# Log errors to a file if we have write access to file or config dir
|
||||||
err_log_path = hass.config.path("home-assistant.log")
|
err_log_path = hass.config.path('home-assistant.log')
|
||||||
err_path_exists = os.path.isfile(err_log_path)
|
err_path_exists = os.path.isfile(err_log_path)
|
||||||
|
|
||||||
# Check if we can write to the error log if it exists or that
|
# Check if we can write to the error log if it exists or that
|
||||||
@ -196,30 +207,73 @@ def enable_logging(hass):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Unable to setup error log %s (access denied)", err_log_path)
|
'Unable to setup error log %s (access denied)', err_log_path)
|
||||||
|
|
||||||
|
|
||||||
def process_ha_core_config(hass, config):
|
def process_ha_core_config(hass, config):
|
||||||
""" Processes the [homeassistant] section from the config. """
|
""" Processes the [homeassistant] section from the config. """
|
||||||
|
hac = hass.config
|
||||||
|
|
||||||
|
def set_time_zone(time_zone_str):
|
||||||
|
""" Helper method to set time zone in HA. """
|
||||||
|
if time_zone_str is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
time_zone = date_util.get_time_zone(time_zone_str)
|
||||||
|
|
||||||
|
if time_zone:
|
||||||
|
hac.time_zone = time_zone
|
||||||
|
date_util.set_default_time_zone(time_zone)
|
||||||
|
else:
|
||||||
|
_LOGGER.error('Received invalid time zone %s', time_zone_str)
|
||||||
|
|
||||||
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
||||||
(CONF_LONGITUDE, 'longitude'),
|
(CONF_LONGITUDE, 'longitude'),
|
||||||
(CONF_NAME, 'location_name'),
|
(CONF_NAME, 'location_name')):
|
||||||
(CONF_TIME_ZONE, 'time_zone')):
|
|
||||||
if key in config:
|
if key in config:
|
||||||
setattr(hass.config, attr, config[key])
|
setattr(hac, attr, config[key])
|
||||||
|
|
||||||
for entity_id, hidden in config.get(CONF_VISIBILITY, {}).items():
|
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||||
Entity.overwrite_hidden(entity_id, hidden == 'hide')
|
|
||||||
|
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
|
||||||
|
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
|
||||||
|
|
||||||
if CONF_TEMPERATURE_UNIT in config:
|
if CONF_TEMPERATURE_UNIT in config:
|
||||||
unit = config[CONF_TEMPERATURE_UNIT]
|
unit = config[CONF_TEMPERATURE_UNIT]
|
||||||
|
|
||||||
if unit == 'C':
|
if unit == 'C':
|
||||||
hass.config.temperature_unit = TEMP_CELCIUS
|
hac.temperature_unit = TEMP_CELCIUS
|
||||||
elif unit == 'F':
|
elif unit == 'F':
|
||||||
hass.config.temperature_unit = TEMP_FAHRENHEIT
|
hac.temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
|
||||||
hass.config.auto_detect()
|
# If we miss some of the needed values, auto detect them
|
||||||
|
if None not in (
|
||||||
|
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info('Auto detecting location and temperature unit')
|
||||||
|
|
||||||
|
info = util.detect_location_info()
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
_LOGGER.error('Could not detect location information')
|
||||||
|
return
|
||||||
|
|
||||||
|
if hac.latitude is None and hac.longitude is None:
|
||||||
|
hac.latitude = info.latitude
|
||||||
|
hac.longitude = info.longitude
|
||||||
|
|
||||||
|
if hac.temperature_unit is None:
|
||||||
|
if info.use_fahrenheit:
|
||||||
|
hac.temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
else:
|
||||||
|
hac.temperature_unit = TEMP_CELCIUS
|
||||||
|
|
||||||
|
if hac.location_name is None:
|
||||||
|
hac.location_name = info.city
|
||||||
|
|
||||||
|
if hac.time_zone is None:
|
||||||
|
set_time_zone(info.time_zone)
|
||||||
|
|
||||||
|
|
||||||
def _ensure_loader_prepared(hass):
|
def _ensure_loader_prepared(hass):
|
||||||
|
@ -15,6 +15,7 @@ import homeassistant.remote as rem
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,
|
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,
|
||||||
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS,
|
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS,
|
||||||
|
URL_API_CONFIG, URL_API_BOOTSTRAP,
|
||||||
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
|
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
|
||||||
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
|
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
|
||||||
HTTP_UNPROCESSABLE_ENTITY)
|
HTTP_UNPROCESSABLE_ENTITY)
|
||||||
@ -42,6 +43,13 @@ def setup(hass, config):
|
|||||||
# /api/stream
|
# /api/stream
|
||||||
hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream)
|
hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream)
|
||||||
|
|
||||||
|
# /api/config
|
||||||
|
hass.http.register_path('GET', URL_API_CONFIG, _handle_get_api_config)
|
||||||
|
|
||||||
|
# /api/bootstrap
|
||||||
|
hass.http.register_path(
|
||||||
|
'GET', URL_API_BOOTSTRAP, _handle_get_api_bootstrap)
|
||||||
|
|
||||||
# /states
|
# /states
|
||||||
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
|
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
|
||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
@ -140,6 +148,23 @@ def _handle_get_api_stream(handler, path_match, data):
|
|||||||
hass.bus.remove_listener(MATCH_ALL, forward_events)
|
hass.bus.remove_listener(MATCH_ALL, forward_events)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_get_api_config(handler, path_match, data):
|
||||||
|
""" Returns the Home Assistant config. """
|
||||||
|
handler.write_json(handler.server.hass.config.as_dict())
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_get_api_bootstrap(handler, path_match, data):
|
||||||
|
""" Returns all data needed to bootstrap Home Assistant. """
|
||||||
|
hass = handler.server.hass
|
||||||
|
|
||||||
|
handler.write_json({
|
||||||
|
'config': hass.config.as_dict(),
|
||||||
|
'states': hass.states.all(),
|
||||||
|
'events': _events_json(hass),
|
||||||
|
'services': _services_json(hass),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def _handle_get_api_states(handler, path_match, data):
|
def _handle_get_api_states(handler, path_match, data):
|
||||||
""" Returns a dict containing all entity ids and their state. """
|
""" Returns a dict containing all entity ids and their state. """
|
||||||
handler.write_json(handler.server.hass.states.all())
|
handler.write_json(handler.server.hass.states.all())
|
||||||
@ -190,9 +215,7 @@ def _handle_post_state_entity(handler, path_match, data):
|
|||||||
|
|
||||||
def _handle_get_api_events(handler, path_match, data):
|
def _handle_get_api_events(handler, path_match, data):
|
||||||
""" Handles getting overview of event listeners. """
|
""" Handles getting overview of event listeners. """
|
||||||
handler.write_json([{"event": key, "listener_count": value}
|
handler.write_json(_events_json(handler.server.hass))
|
||||||
for key, value
|
|
||||||
in handler.server.hass.bus.listeners.items()])
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_api_post_events_event(handler, path_match, event_data):
|
def _handle_api_post_events_event(handler, path_match, event_data):
|
||||||
@ -227,10 +250,7 @@ def _handle_api_post_events_event(handler, path_match, event_data):
|
|||||||
|
|
||||||
def _handle_get_api_services(handler, path_match, data):
|
def _handle_get_api_services(handler, path_match, data):
|
||||||
""" Handles getting overview of services. """
|
""" Handles getting overview of services. """
|
||||||
handler.write_json(
|
handler.write_json(_services_json(handler.server.hass))
|
||||||
[{"domain": key, "services": value}
|
|
||||||
for key, value
|
|
||||||
in handler.server.hass.services.services.items()])
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@ -312,3 +332,15 @@ def _handle_get_api_components(handler, path_match, data):
|
|||||||
""" Returns all the loaded components. """
|
""" Returns all the loaded components. """
|
||||||
|
|
||||||
handler.write_json(handler.server.hass.config.components)
|
handler.write_json(handler.server.hass.config.components)
|
||||||
|
|
||||||
|
|
||||||
|
def _services_json(hass):
|
||||||
|
""" Generate services data to JSONify. """
|
||||||
|
return [{"domain": key, "services": value}
|
||||||
|
for key, value in hass.services.services.items()]
|
||||||
|
|
||||||
|
|
||||||
|
def _events_json(hass):
|
||||||
|
""" Generate event data to JSONify. """
|
||||||
|
return [{"event": key, "listener_count": value}
|
||||||
|
for key, value in hass.bus.listeners.items()]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.demo
|
homeassistant.components.demo
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Sets up a demo environment that mimics interaction with devices
|
Sets up a demo environment that mimics interaction with devices.
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -32,7 +32,13 @@ def setup(hass, config):
|
|||||||
hass.states.set('a.Demo_Mode', 'Enabled')
|
hass.states.set('a.Demo_Mode', 'Enabled')
|
||||||
|
|
||||||
# Setup sun
|
# Setup sun
|
||||||
loader.get_component('sun').setup(hass, config)
|
if not hass.config.latitude:
|
||||||
|
hass.config.latitude = '32.87336'
|
||||||
|
|
||||||
|
if not hass.config.longitude:
|
||||||
|
hass.config.longitude = '117.22743'
|
||||||
|
|
||||||
|
bootstrap.setup_component(hass, 'sun')
|
||||||
|
|
||||||
# Setup demo platforms
|
# Setup demo platforms
|
||||||
for component in COMPONENTS_WITH_DEMO_PLATFORM:
|
for component in COMPONENTS_WITH_DEMO_PLATFORM:
|
||||||
|
@ -6,8 +6,9 @@ Provides functionality to turn on lights based on
|
|||||||
the state of the sun and devices.
|
the state of the sun and devices.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ def setup(hass, config):
|
|||||||
new_state.state == STATE_HOME:
|
new_state.state == STATE_HOME:
|
||||||
|
|
||||||
# These variables are needed for the elif check
|
# These variables are needed for the elif check
|
||||||
now = datetime.now()
|
now = dt_util.now()
|
||||||
start_point = calc_time_for_light_when_sunset()
|
start_point = calc_time_for_light_when_sunset()
|
||||||
|
|
||||||
# Do we need lights?
|
# Do we need lights?
|
||||||
|
@ -8,11 +8,12 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
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,
|
||||||
@ -113,7 +114,7 @@ class DeviceTracker(object):
|
|||||||
""" Reload known devices file. """
|
""" Reload known devices file. """
|
||||||
self._read_known_devices_file()
|
self._read_known_devices_file()
|
||||||
|
|
||||||
self.update_devices(datetime.now())
|
self.update_devices(dt_util.utcnow())
|
||||||
|
|
||||||
dev_group.update_tracked_entity_ids(self.device_entity_ids)
|
dev_group.update_tracked_entity_ids(self.device_entity_ids)
|
||||||
|
|
||||||
@ -125,7 +126,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_time_change(update_device_state, second=seconds)
|
hass.track_utc_time_change(update_device_state, second=seconds)
|
||||||
|
|
||||||
hass.services.register(DOMAIN,
|
hass.services.register(DOMAIN,
|
||||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||||
@ -226,7 +227,7 @@ class DeviceTracker(object):
|
|||||||
self.untracked_devices.clear()
|
self.untracked_devices.clear()
|
||||||
|
|
||||||
with open(known_dev_path) as inp:
|
with open(known_dev_path) as inp:
|
||||||
default_last_seen = datetime(1990, 1, 1)
|
default_last_seen = dt_util.utcnow().replace(year=1990)
|
||||||
|
|
||||||
# To track which devices need an entity_id assigned
|
# To track which devices need an entity_id assigned
|
||||||
need_entity_id = []
|
need_entity_id = []
|
||||||
|
@ -1,4 +1,35 @@
|
|||||||
""" Supports scanning a DD-WRT router. """
|
"""
|
||||||
|
homeassistant.components.device_tracker.ddwrt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a DD-WRT router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the DD-WRT tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: ddwrt
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import re
|
import re
|
||||||
@ -20,7 +51,7 @@ _DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
""" Validates config and returns a DdWrt scanner. """
|
""" Validates config and returns a DD-WRT scanner. """
|
||||||
if not validate_config(config,
|
if not validate_config(config,
|
||||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
_LOGGER):
|
_LOGGER):
|
||||||
@ -93,7 +124,7 @@ class DdWrtDeviceScanner(object):
|
|||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
def _update_info(self):
|
def _update_info(self):
|
||||||
""" Ensures the information from the DdWrt router is up to date.
|
""" Ensures the information from the DD-WRT router is up to date.
|
||||||
Returns boolean if scanning successful. """
|
Returns boolean if scanning successful. """
|
||||||
if not self.success_init:
|
if not self.success_init:
|
||||||
return False
|
return False
|
||||||
@ -111,8 +142,8 @@ class DdWrtDeviceScanner(object):
|
|||||||
self.last_results = []
|
self.last_results = []
|
||||||
active_clients = data.get('active_wireless', None)
|
active_clients = data.get('active_wireless', None)
|
||||||
if active_clients:
|
if active_clients:
|
||||||
# This is really lame, instead of using JSON the ddwrt UI
|
# This is really lame, instead of using JSON the DD-WRT UI
|
||||||
# uses it's own data format for some reason and then
|
# uses its own data format for some reason and then
|
||||||
# regex's out values so I guess I have to do the same,
|
# regex's out values so I guess I have to do the same,
|
||||||
# LAME!!!
|
# LAME!!!
|
||||||
|
|
||||||
@ -132,7 +163,7 @@ class DdWrtDeviceScanner(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_ddwrt_data(self, url):
|
def get_ddwrt_data(self, url):
|
||||||
""" Retrieve data from DD-WRT and return parsed result """
|
""" Retrieve data from DD-WRT and return parsed result. """
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
url,
|
url,
|
||||||
@ -154,8 +185,7 @@ class DdWrtDeviceScanner(object):
|
|||||||
|
|
||||||
|
|
||||||
def _parse_ddwrt_response(data_str):
|
def _parse_ddwrt_response(data_str):
|
||||||
""" Parse the awful DD-WRT data format, why didn't they use JSON????.
|
""" Parse the DD-WRT data format. """
|
||||||
This code is a python version of how they are parsing in the JS """
|
|
||||||
return {
|
return {
|
||||||
key: val for key, val in _DDWRT_DATA_REGEX
|
key: val for key, val in _DDWRT_DATA_REGEX
|
||||||
.findall(data_str)}
|
.findall(data_str)}
|
||||||
|
@ -1,4 +1,39 @@
|
|||||||
""" Supports scanning a OpenWRT router. """
|
"""
|
||||||
|
homeassistant.components.device_tracker.luci
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a OpenWRT router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
|
||||||
|
It's required that the luci RPC package is installed on the OpenWRT router:
|
||||||
|
# opkg install luci-mod-rpc
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Luci tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: luci
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -1,4 +1,35 @@
|
|||||||
""" Supports scanning a Netgear router. """
|
"""
|
||||||
|
homeassistant.components.device_tracker.netgear
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a Netgear router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Netgear tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: netgear
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import threading
|
import threading
|
||||||
@ -30,7 +61,7 @@ def get_scanner(hass, config):
|
|||||||
|
|
||||||
|
|
||||||
class NetgearDeviceScanner(object):
|
class NetgearDeviceScanner(object):
|
||||||
""" This class queries a Netgear wireless router using the SOAP-api. """
|
""" This class queries a Netgear wireless router using the SOAP-API. """
|
||||||
|
|
||||||
def __init__(self, host, username, password):
|
def __init__(self, host, username, password):
|
||||||
self.last_results = []
|
self.last_results = []
|
||||||
|
@ -1,6 +1,27 @@
|
|||||||
""" Supports scanning using nmap. """
|
"""
|
||||||
|
homeassistant.components.device_tracker.nmap
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a network with nmap.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the nmap tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: nmap_tracker
|
||||||
|
hosts: 192.168.1.1/24
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
hosts
|
||||||
|
*Required
|
||||||
|
The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or
|
||||||
|
the range notation (192.168.1.1-255).
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
@ -8,6 +29,7 @@ import re
|
|||||||
from libnmap.process import NmapProcess
|
from libnmap.process import NmapProcess
|
||||||
from libnmap.parser import NmapParser, NmapParserException
|
from libnmap.parser import NmapParser, NmapParserException
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import CONF_HOSTS
|
from homeassistant.const import CONF_HOSTS
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.util import Throttle, convert
|
from homeassistant.util import Throttle, convert
|
||||||
@ -36,7 +58,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
|
|||||||
|
|
||||||
|
|
||||||
def _arp(ip_address):
|
def _arp(ip_address):
|
||||||
""" Get the MAC address for a given IP """
|
""" Get the MAC address for a given IP. """
|
||||||
cmd = ['arp', '-n', ip_address]
|
cmd = ['arp', '-n', ip_address]
|
||||||
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||||
out, _ = arp.communicate()
|
out, _ = arp.communicate()
|
||||||
@ -85,7 +107,7 @@ class NmapDeviceScanner(object):
|
|||||||
Returns True if successful, False otherwise. """
|
Returns True if successful, False otherwise. """
|
||||||
try:
|
try:
|
||||||
results = NmapParser.parse(stdout)
|
results = NmapParser.parse(stdout)
|
||||||
now = datetime.now()
|
now = dt_util.now()
|
||||||
self.last_results = []
|
self.last_results = []
|
||||||
for host in results.hosts:
|
for host in results.hosts:
|
||||||
if host.is_up():
|
if host.is_up():
|
||||||
@ -119,7 +141,7 @@ class NmapDeviceScanner(object):
|
|||||||
options = "-F --host-timeout 5"
|
options = "-F --host-timeout 5"
|
||||||
exclude_targets = set()
|
exclude_targets = set()
|
||||||
if self.home_interval:
|
if self.home_interval:
|
||||||
now = datetime.now()
|
now = dt_util.now()
|
||||||
for host in self.last_results:
|
for host in self.last_results:
|
||||||
if host.last_update + self.home_interval > now:
|
if host.last_update + self.home_interval > now:
|
||||||
exclude_targets.add(host)
|
exclude_targets.add(host)
|
||||||
|
@ -1,4 +1,41 @@
|
|||||||
""" Supports scanning a Tomato router. """
|
"""
|
||||||
|
homeassistant.components.device_tracker.tomato
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Device tracker platform that supports scanning a Tomato router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Tomato tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: tomato
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
http_id: ABCDEFG
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
|
||||||
|
http_id
|
||||||
|
*Required
|
||||||
|
The value can be obtained by logging in to the Tomato admin interface and
|
||||||
|
search for http_id in the page source code.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.discovery
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Starts a service to scan in intervals for new devices.
|
Starts a service to scan in intervals for new devices.
|
||||||
|
|
||||||
Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
|
Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
|
||||||
|
|
||||||
Knows which components handle certain types, will make sure they are
|
Knows which components handle certain types, will make sure they are
|
||||||
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
|
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "93774c7a1643c7e3f9cbbb1554b36683"
|
VERSION = "28c0680cf6ebd969dc5710c22d9c4075"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -11,7 +11,7 @@
|
|||||||
"bower_components"
|
"bower_components"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"webcomponentsjs": "Polymer/webcomponentsjs#~0.5.5",
|
"webcomponentsjs": "Polymer/webcomponentsjs#~0.6",
|
||||||
"font-roboto": "Polymer/font-roboto#~0.5.5",
|
"font-roboto": "Polymer/font-roboto#~0.5.5",
|
||||||
"core-header-panel": "polymer/core-header-panel#~0.5.5",
|
"core-header-panel": "polymer/core-header-panel#~0.5.5",
|
||||||
"core-toolbar": "polymer/core-toolbar#~0.5.5",
|
"core-toolbar": "polymer/core-toolbar#~0.5.5",
|
||||||
@ -36,9 +36,12 @@
|
|||||||
"paper-slider": "polymer/paper-slider#~0.5.5",
|
"paper-slider": "polymer/paper-slider#~0.5.5",
|
||||||
"paper-checkbox": "polymer/paper-checkbox#~0.5.5",
|
"paper-checkbox": "polymer/paper-checkbox#~0.5.5",
|
||||||
"color-picker-element": "~0.0.2",
|
"color-picker-element": "~0.0.2",
|
||||||
"google-apis": "GoogleWebComponents/google-apis#~0.4.2",
|
"google-apis": "GoogleWebComponents/google-apis#~0.4.4",
|
||||||
"core-drawer-panel": "polymer/core-drawer-panel#~0.5.5",
|
"core-drawer-panel": "polymer/core-drawer-panel#~0.5.5",
|
||||||
"core-scroll-header-panel": "polymer/core-scroll-header-panel#~0.5.5",
|
"core-scroll-header-panel": "polymer/core-scroll-header-panel#~0.5.5",
|
||||||
"moment": "~2.9.0"
|
"moment": "~2.10.2"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"webcomponentsjs": "~0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,17 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var timeFormatOptions = {hour: 'numeric', minute: '2-digit'};
|
var uiUtil = window.hass.uiUtil;
|
||||||
|
|
||||||
Polymer({
|
Polymer({
|
||||||
time: "",
|
time: "",
|
||||||
|
|
||||||
dateObjChanged: function(oldVal, newVal) {
|
dateObjChanged: function(oldVal, newVal) {
|
||||||
if (!newVal) {
|
if (newVal) {
|
||||||
|
this.time = uiUtil.formatTime(newVal);
|
||||||
|
} else {
|
||||||
this.time = "";
|
this.time = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.time = newVal.toLocaleTimeString([], timeFormatOptions);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<link rel="import" href="../resources/moment-js.html">
|
<link rel="import" href="../resources/moment-js.html">
|
||||||
|
|
||||||
<polymer-element name="relative-ha-datetime" attributes="datetime">
|
<polymer-element name="relative-ha-datetime" attributes="datetime datetimeObj">
|
||||||
<template>
|
<template>
|
||||||
{{ relativeTime }}
|
{{ relativeTime }}
|
||||||
</template>
|
</template>
|
||||||
@ -34,8 +34,15 @@
|
|||||||
this.updateRelative();
|
this.updateRelative();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
datetimeObjChanged: function(oldVal, newVal) {
|
||||||
|
this.parsedDateTime = newVal;
|
||||||
|
|
||||||
|
this.updateRelative();
|
||||||
|
},
|
||||||
|
|
||||||
updateRelative: function() {
|
updateRelative: function() {
|
||||||
this.relativeTime = this.parsedDateTime ? moment(this.parsedDateTime).fromNow() : "";
|
this.relativeTime = this.parsedDateTime ?
|
||||||
|
moment(this.parsedDateTime).fromNow() : "";
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="time-ago">
|
<div class="time-ago">
|
||||||
<core-tooltip label="{{stateObj.lastChanged}}" position="bottom">
|
<core-tooltip label="{{stateObj.lastChangedAsDate | formatDateTime}}" position="bottom">
|
||||||
<relative-ha-datetime datetime="{{stateObj.lastChanged}}"></relative-ha-datetime>
|
<relative-ha-datetime datetimeObj="{{stateObj.lastChangedAsDate}}"></relative-ha-datetime>
|
||||||
</core-tooltip>
|
</core-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<state-card-content stateObj="{{stateObj}}" style='margin-bottom: 24px;'>
|
<state-card-content stateObj="{{stateObj}}" style='margin-bottom: 24px;'>
|
||||||
</state-card-content>
|
</state-card-content>
|
||||||
<state-timeline stateHistory="{{stateHistory}}" isLoadingData="{{isLoadingHistoryData}}"></state-timeline>
|
<template if="{{hasHistoryComponent}}">
|
||||||
|
<state-timeline stateHistory="{{stateHistory}}" isLoadingData="{{isLoadingHistoryData}}"></state-timeline>
|
||||||
|
</template>
|
||||||
<more-info-content
|
<more-info-content
|
||||||
stateObj="{{stateObj}}"
|
stateObj="{{stateObj}}"
|
||||||
dialogOpen="{{dialogOpen}}"></more-info-content>
|
dialogOpen="{{dialogOpen}}"></more-info-content>
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 56f896efa573aaa9554812a3c41b78278bce2064
|
Subproject commit 69ee1c49af12caf00655c66d56474b5c1bcac1c1
|
@ -11,38 +11,43 @@
|
|||||||
|
|
||||||
<div layout justified horizontal class='data-entry' id='rising'>
|
<div layout justified horizontal class='data-entry' id='rising'>
|
||||||
<div class='key'>
|
<div class='key'>
|
||||||
Rising <relative-ha-datetime datetime="{{stateObj.attributes.next_rising}}"></relative-ha-datetime>
|
Rising <relative-ha-datetime datetimeObj="{{rising}}"></relative-ha-datetime>
|
||||||
</div>
|
</div>
|
||||||
<div class='value'>
|
<div class='value'>
|
||||||
{{stateObj.attributes.next_rising | HATimeStripDate}}
|
{{rising | formatTime}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div layout justified horizontal class='data-entry' id='setting'>
|
<div layout justified horizontal class='data-entry' id='setting'>
|
||||||
<div class='key'>
|
<div class='key'>
|
||||||
Setting <relative-ha-datetime datetime="{{stateObj.attributes.next_setting}}"></relative-ha-datetime>
|
Setting <relative-ha-datetime datetimeObj="{{setting}}"></relative-ha-datetime>
|
||||||
</div>
|
</div>
|
||||||
<div class='value'>
|
<div class='value'>
|
||||||
{{stateObj.attributes.next_setting | HATimeStripDate}}
|
{{setting | formatTime}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
(function() {
|
||||||
var parseDateTime = window.hass.util.parseDateTime;
|
var parseDateTime = window.hass.util.parseDateTime;
|
||||||
|
|
||||||
Polymer({
|
Polymer({
|
||||||
stateObjChanged: function() {
|
rising: null,
|
||||||
var rising = parseDateTime(this.stateObj.attributes.next_rising);
|
setting: null,
|
||||||
var setting = parseDateTime(this.stateObj.attributes.next_setting);
|
|
||||||
|
|
||||||
if(rising > setting) {
|
stateObjChanged: function() {
|
||||||
|
this.rising = parseDateTime(this.stateObj.attributes.next_rising);
|
||||||
|
this.setting = parseDateTime(this.stateObj.attributes.next_setting);
|
||||||
|
|
||||||
|
if(self.rising > self.setting) {
|
||||||
this.$.sunData.appendChild(this.$.rising);
|
this.$.sunData.appendChild(this.$.rising);
|
||||||
} else {
|
} else {
|
||||||
this.$.sunData.appendChild(this.$.setting);
|
this.$.sunData.appendChild(this.$.setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</polymer-element>
|
</polymer-element>
|
||||||
|
@ -7,18 +7,6 @@
|
|||||||
'light', 'group', 'sun', 'configurator', 'thermostat', 'script'
|
'light', 'group', 'sun', 'configurator', 'thermostat', 'script'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register some polymer filters
|
|
||||||
|
|
||||||
PolymerExpressions.prototype.HATimeToDate = function(timeString) {
|
|
||||||
if (!timeString) return;
|
|
||||||
|
|
||||||
return window.hass.util.parseDateTime(timeString);
|
|
||||||
};
|
|
||||||
|
|
||||||
PolymerExpressions.prototype.HATimeStripDate = function(timeString) {
|
|
||||||
return (timeString || "").split(' ')[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add some frontend specific helpers to the models
|
// Add some frontend specific helpers to the models
|
||||||
Object.defineProperties(window.hass.stateModel.prototype, {
|
Object.defineProperties(window.hass.stateModel.prototype, {
|
||||||
// how to render the card for this state
|
// how to render the card for this state
|
||||||
@ -81,3 +69,5 @@
|
|||||||
window.hass.uiUtil = {};
|
window.hass.uiUtil = {};
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<link rel="import" href="./moment-js.html">
|
||||||
|
@ -3,3 +3,22 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script src="../bower_components/moment/moment.js"></script>
|
<script src="../bower_components/moment/moment.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.hass.uiUtil.formatTime = function(dateObj) {
|
||||||
|
return moment(dateObj).format('LT');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.hass.uiUtil.formatDateTime = function(dateObj) {
|
||||||
|
return moment(dateObj).format('lll');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.hass.uiUtil.formatDate = function(dateObj) {
|
||||||
|
return moment(dateObj).format('ll');
|
||||||
|
};
|
||||||
|
|
||||||
|
PolymerExpressions.prototype.formatTime = window.hass.uiUtil.formatTime;
|
||||||
|
PolymerExpressions.prototype.formatDateTime = window.hass.uiUtil.formatDateTime;
|
||||||
|
PolymerExpressions.prototype.formatDate = window.hass.uiUtil.formatDate;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.groups
|
homeassistant.components.group
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides functionality to group devices that can be turned on or off.
|
Provides functionality to group devices that can be turned on or off.
|
||||||
"""
|
"""
|
||||||
|
@ -5,10 +5,11 @@ homeassistant.components.history
|
|||||||
Provide pre-made queries on top of the recorder component.
|
Provide pre-made queries on top of the recorder component.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
import homeassistant.components.recorder as recorder
|
import homeassistant.components.recorder as recorder
|
||||||
|
|
||||||
DOMAIN = 'history'
|
DOMAIN = 'history'
|
||||||
@ -22,7 +23,7 @@ def last_5_states(entity_id):
|
|||||||
query = """
|
query = """
|
||||||
SELECT * FROM states WHERE entity_id=? AND
|
SELECT * FROM states WHERE entity_id=? AND
|
||||||
last_changed=last_updated
|
last_changed=last_updated
|
||||||
ORDER BY last_changed DESC LIMIT 0, 5
|
ORDER BY state_id DESC LIMIT 0, 5
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return recorder.query_states(query, (entity_id, ))
|
return recorder.query_states(query, (entity_id, ))
|
||||||
@ -30,7 +31,7 @@ def last_5_states(entity_id):
|
|||||||
|
|
||||||
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
||||||
"""
|
"""
|
||||||
Return states changes during period start_time - end_time.
|
Return states changes during UTC period start_time - end_time.
|
||||||
"""
|
"""
|
||||||
where = "last_changed=last_updated AND last_changed > ? "
|
where = "last_changed=last_updated AND last_changed > ? "
|
||||||
data = [start_time]
|
data = [start_time]
|
||||||
@ -64,17 +65,17 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_states(point_in_time, entity_ids=None, run=None):
|
def get_states(utc_point_in_time, entity_ids=None, run=None):
|
||||||
""" Returns the states at a specific point in time. """
|
""" Returns the states at a specific point in time. """
|
||||||
if run is None:
|
if run is None:
|
||||||
run = recorder.run_information(point_in_time)
|
run = recorder.run_information(utc_point_in_time)
|
||||||
|
|
||||||
# History did not run before point_in_time
|
# History did not run before utc_point_in_time
|
||||||
if run is None:
|
if run is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
where = run.where_after_start_run + "AND created < ? "
|
where = run.where_after_start_run + "AND created < ? "
|
||||||
where_data = [point_in_time]
|
where_data = [utc_point_in_time]
|
||||||
|
|
||||||
if entity_ids is not None:
|
if entity_ids is not None:
|
||||||
where += "AND entity_id IN ({}) ".format(
|
where += "AND entity_id IN ({}) ".format(
|
||||||
@ -93,9 +94,9 @@ def get_states(point_in_time, entity_ids=None, run=None):
|
|||||||
return recorder.query_states(query, where_data)
|
return recorder.query_states(query, where_data)
|
||||||
|
|
||||||
|
|
||||||
def get_state(point_in_time, entity_id, run=None):
|
def get_state(utc_point_in_time, entity_id, run=None):
|
||||||
""" Return a state at a specific point in time. """
|
""" Return a state at a specific point in time. """
|
||||||
states = get_states(point_in_time, (entity_id,), run)
|
states = get_states(utc_point_in_time, (entity_id,), run)
|
||||||
|
|
||||||
return states[0] if states else None
|
return states[0] if states else None
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ def _api_last_5_states(handler, path_match, data):
|
|||||||
def _api_history_period(handler, path_match, data):
|
def _api_history_period(handler, path_match, data):
|
||||||
""" Return history over a period of time. """
|
""" Return history over a period of time. """
|
||||||
# 1 day for now..
|
# 1 day for now..
|
||||||
start_time = datetime.now() - timedelta(seconds=86400)
|
start_time = date_util.utcnow() - timedelta(seconds=86400)
|
||||||
|
|
||||||
entity_id = data.get('filter_entity_id')
|
entity_id = data.get('filter_entity_id')
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.httpinterface
|
homeassistant.components.httpinterface
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module provides an API and a HTTP interface for debug purposes.
|
This module provides an API and a HTTP interface for debug purposes.
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.isy994
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Connects to an ISY-994 controller and loads relevant components to control its
|
Connects to an ISY-994 controller and loads relevant components to control its
|
||||||
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.
|
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.
|
||||||
"""
|
"""
|
||||||
@ -13,7 +16,8 @@ from homeassistant.helpers import validate_config
|
|||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED,
|
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED,
|
||||||
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
|
EVENT_HOMEASSISTANT_STOP, ATTR_SERVICE, ATTR_DISCOVERED,
|
||||||
|
ATTR_FRIENDLY_NAME)
|
||||||
|
|
||||||
# homeassistant constants
|
# homeassistant constants
|
||||||
DOMAIN = "isy994"
|
DOMAIN = "isy994"
|
||||||
@ -31,7 +35,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""
|
"""
|
||||||
Setup isy994 component.
|
Setup ISY994 component.
|
||||||
This will automatically import associated lights, switches, and sensors.
|
This will automatically import associated lights, switches, and sensors.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -77,6 +81,9 @@ def setup(hass, config):
|
|||||||
if not ISY.connected:
|
if not ISY.connected:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# listen for HA stop to disconnect
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
|
||||||
|
|
||||||
# Load components for the devices in the ISY controller that we support
|
# Load components for the devices in the ISY controller that we support
|
||||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||||
('light', DISCOVER_LIGHTS),
|
('light', DISCOVER_LIGHTS),
|
||||||
@ -91,8 +98,13 @@ def setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def stop(event):
|
||||||
|
""" Cleanup the ISY subscription. """
|
||||||
|
ISY.auto_update = False
|
||||||
|
|
||||||
|
|
||||||
class ISYDeviceABC(ToggleEntity):
|
class ISYDeviceABC(ToggleEntity):
|
||||||
""" Abstract Class for an ISY device within home assistant. """
|
""" Abstract Class for an ISY device. """
|
||||||
|
|
||||||
_attrs = {}
|
_attrs = {}
|
||||||
_onattrs = []
|
_onattrs = []
|
||||||
@ -133,7 +145,7 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
""" returns the unclean value from the controller """
|
""" Returns the unclean value from the controller. """
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return self.node.status._val
|
return self.node.status._val
|
||||||
|
|
||||||
@ -147,7 +159,7 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
""" Returns the id of this isy sensor """
|
""" Returns the id of this ISY sensor. """
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return self.node._id
|
return self.node._id
|
||||||
|
|
||||||
@ -190,7 +202,7 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" turns the device on """
|
""" Turns the device on. """
|
||||||
if self.domain is not 'sensor':
|
if self.domain is not 'sensor':
|
||||||
attrs = [kwargs.get(name) for name in self._onattrs]
|
attrs = [kwargs.get(name) for name in self._onattrs]
|
||||||
self.node.on(*attrs)
|
self.node.on(*attrs)
|
||||||
@ -198,7 +210,7 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
_LOGGER.error('ISY cannot turn on sensors.')
|
_LOGGER.error('ISY cannot turn on sensors.')
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" turns the device off """
|
""" Turns the device off. """
|
||||||
if self.domain is not 'sensor':
|
if self.domain is not 'sensor':
|
||||||
self.node.off()
|
self.node.off()
|
||||||
else:
|
else:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.keyboard
|
homeassistant.components.keyboard
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides functionality to emulate keyboard presses on host machine.
|
Provides functionality to emulate keyboard presses on host machine.
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
""" Provides demo lights. """
|
"""
|
||||||
|
homeassistant.components.light.demo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Demo platform that implements lights.
|
||||||
|
|
||||||
|
"""
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.logbook
|
homeassistant.components.logbook
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Parses events and generates a human log
|
Parses events and generates a human log.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from homeassistant import State, DOMAIN as HA_DOMAIN
|
from homeassistant import State, DOMAIN as HA_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
import homeassistant.util as util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.recorder as recorder
|
import homeassistant.components.recorder as recorder
|
||||||
import homeassistant.components.sun as sun
|
import homeassistant.components.sun as sun
|
||||||
|
|
||||||
@ -38,10 +37,11 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def _handle_get_logbook(handler, path_match, data):
|
def _handle_get_logbook(handler, path_match, data):
|
||||||
""" Return logbook entries. """
|
""" Return logbook entries. """
|
||||||
start_today = datetime.now().date()
|
start_today = dt_util.now().replace(hour=0, minute=0, second=0)
|
||||||
|
|
||||||
handler.write_json(humanify(
|
handler.write_json(humanify(
|
||||||
recorder.query_events(QUERY_EVENTS_AFTER, (start_today,))))
|
recorder.query_events(
|
||||||
|
QUERY_EVENTS_AFTER, (dt_util.as_utc(start_today),))))
|
||||||
|
|
||||||
|
|
||||||
class Entry(object):
|
class Entry(object):
|
||||||
@ -60,7 +60,7 @@ class Entry(object):
|
|||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Convert Entry to a dict to be used within JSON. """
|
""" Convert Entry to a dict to be used within JSON. """
|
||||||
return {
|
return {
|
||||||
'when': util.datetime_to_str(self.when),
|
'when': dt_util.datetime_to_str(self.when),
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'message': self.message,
|
'message': self.message,
|
||||||
'domain': self.domain,
|
'domain': self.domain,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.media_player.chromecast
|
homeassistant.components.media_player.demo
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Demo implementation of the media player.
|
Demo implementation of the media player.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
|
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
|
||||||
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION,
|
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
components.modbus
|
homeassistant.components.modbus
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Modbus component, using pymodbus (python3 branch)
|
Modbus component, using pymodbus (python3 branch)
|
||||||
|
|
||||||
typical declaration in configuration.yaml
|
Configuration:
|
||||||
|
|
||||||
|
To use the Modbus component you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
#Modbus TCP
|
#Modbus TCP
|
||||||
modbus:
|
modbus:
|
||||||
@ -27,10 +30,8 @@ import logging
|
|||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
# The domain of your component. Should be equal to the name of your component
|
|
||||||
DOMAIN = "modbus"
|
DOMAIN = "modbus"
|
||||||
|
|
||||||
# List of component names (string) your component depends upon
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
|
|
||||||
# Type of network
|
# Type of network
|
||||||
@ -86,11 +87,11 @@ def setup(hass, config):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def stop_modbus(event):
|
def stop_modbus(event):
|
||||||
""" Stop Modbus service"""
|
""" Stop Modbus service. """
|
||||||
NETWORK.close()
|
NETWORK.close()
|
||||||
|
|
||||||
def start_modbus(event):
|
def start_modbus(event):
|
||||||
""" Start Modbus service"""
|
""" Start Modbus service. """
|
||||||
NETWORK.connect()
|
NETWORK.connect()
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.notify
|
homeassistant.components.notify
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides functionality to notify people.
|
Provides functionality to notify people.
|
||||||
"""
|
"""
|
||||||
@ -73,7 +73,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class BaseNotificationService(object):
|
class BaseNotificationService(object):
|
||||||
""" Provides an ABC for notifcation services. """
|
""" Provides an ABC for notification services. """
|
||||||
|
|
||||||
def send_message(self, message, **kwargs):
|
def send_message(self, message, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
161
homeassistant/components/notify/instapush.py
Normal file
161
homeassistant/components/notify/instapush.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.instapush
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Instapush notification service.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Instapush notifier you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: instapush
|
||||||
|
api_key: YOUR_APP_KEY
|
||||||
|
app_secret: YOUR_APP_SECRET
|
||||||
|
event: YOUR_EVENT
|
||||||
|
tracker: YOUR_TRACKER
|
||||||
|
|
||||||
|
VARIABLES:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
To retrieve this value log into your account at https://instapush.im and go
|
||||||
|
to 'APPS', choose an app, and check 'Basic Info'.
|
||||||
|
|
||||||
|
app_secret
|
||||||
|
*Required
|
||||||
|
To get this value log into your account at https://instapush.im and go to
|
||||||
|
'APPS'. The 'Application ID' can be found under 'Basic Info'.
|
||||||
|
|
||||||
|
event
|
||||||
|
*Required
|
||||||
|
To retrieve a valid event log into your account at https://instapush.im and go
|
||||||
|
to 'APPS'. If you have no events to use with Home Assistant, create one event
|
||||||
|
for your app.
|
||||||
|
|
||||||
|
tracker
|
||||||
|
*Required
|
||||||
|
To retrieve the tracker value log into your account at https://instapush.im and
|
||||||
|
go to 'APPS', choose the app, and check the event entries.
|
||||||
|
|
||||||
|
Example usage of Instapush if you have an event 'notification' and a tracker
|
||||||
|
'home-assistant'.
|
||||||
|
|
||||||
|
curl -X POST \
|
||||||
|
-H "x-instapush-appid: YOUR_APP_KEY" \
|
||||||
|
-H "x-instapush-appsecret: YOUR_APP_SECRET" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"event":"notification","trackers":{"home-assistant":"Switch 1"}}' \
|
||||||
|
https://api.instapush.im/v1/post
|
||||||
|
|
||||||
|
Details for the API : https://instapush.im/developer/rest
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_RESOURCE = 'https://api.instapush.im/v1/'
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the instapush notification service. """
|
||||||
|
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_API_KEY,
|
||||||
|
'app_secret',
|
||||||
|
'event',
|
||||||
|
'tracker']},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import requests. "
|
||||||
|
"Did you maybe not install the 'Requests' package?")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
try:
|
||||||
|
response = requests.get(_RESOURCE)
|
||||||
|
|
||||||
|
except requests.ConnectionError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Connection error "
|
||||||
|
"Please check if https://instapush.im is available.")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
instapush = requests.Session()
|
||||||
|
headers = {'x-instapush-appid': config[DOMAIN][CONF_API_KEY],
|
||||||
|
'x-instapush-appsecret': config[DOMAIN]['app_secret']}
|
||||||
|
response = instapush.get(_RESOURCE + 'events/list',
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if response.json()['error']:
|
||||||
|
_LOGGER.error(response.json()['msg'])
|
||||||
|
# pylint: disable=bare-except
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
next(events for events in response.json()
|
||||||
|
if events['title'] == config[DOMAIN]['event'])
|
||||||
|
except StopIteration:
|
||||||
|
_LOGGER.error(
|
||||||
|
"No event match your given value. "
|
||||||
|
"Please create an event at https://instapush.im")
|
||||||
|
else:
|
||||||
|
return InstapushNotificationService(
|
||||||
|
config[DOMAIN].get(CONF_API_KEY),
|
||||||
|
config[DOMAIN]['app_secret'],
|
||||||
|
config[DOMAIN]['event'],
|
||||||
|
config[DOMAIN]['tracker']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class InstapushNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for Instapush. """
|
||||||
|
|
||||||
|
def __init__(self, api_key, app_secret, event, tracker):
|
||||||
|
# pylint: disable=no-name-in-module, unused-variable
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
self._api_key = api_key
|
||||||
|
self._app_secret = app_secret
|
||||||
|
self._event = event
|
||||||
|
self._tracker = tracker
|
||||||
|
self._headers = {
|
||||||
|
'x-instapush-appid': self._api_key,
|
||||||
|
'x-instapush-appsecret': self._app_secret,
|
||||||
|
'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
self.instapush = Session()
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
title = kwargs.get(ATTR_TITLE)
|
||||||
|
|
||||||
|
data = {"event": self._event,
|
||||||
|
"trackers": {self._tracker: title + " : " + message}}
|
||||||
|
|
||||||
|
response = self.instapush.post(
|
||||||
|
_RESOURCE + 'post',
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=self._headers)
|
||||||
|
|
||||||
|
if response.json()['status'] == 401:
|
||||||
|
_LOGGER.error(
|
||||||
|
response.json()['msg'],
|
||||||
|
"Please check your details at https://instapush.im/")
|
97
homeassistant/components/notify/nma.py
Normal file
97
homeassistant/components/notify/nma.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.nma
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
NMA (Notify My Android) notification service.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the NMA notifier you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: nma
|
||||||
|
api_key: YOUR_API_KEY
|
||||||
|
|
||||||
|
VARIABLES:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
Enter the API key for NMA. Go to https://www.notifymyandroid.com and create a
|
||||||
|
new API key to use with Home Assistant.
|
||||||
|
|
||||||
|
Details for the API : https://www.notifymyandroid.com/api.jsp
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_RESOURCE = 'https://www.notifymyandroid.com/publicapi/'
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the NMA notification service. """
|
||||||
|
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_API_KEY]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import requests. "
|
||||||
|
"Did you maybe not install the 'Requests' package?")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
nma = Session()
|
||||||
|
response = nma.get(_RESOURCE + 'verify',
|
||||||
|
params={"apikey": config[DOMAIN][CONF_API_KEY]})
|
||||||
|
tree = ET.fromstring(response.content)
|
||||||
|
|
||||||
|
if tree[0].tag == 'error':
|
||||||
|
_LOGGER.error("Wrong API key supplied. %s", tree[0].text)
|
||||||
|
else:
|
||||||
|
return NmaNotificationService(config[DOMAIN][CONF_API_KEY])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class NmaNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for NMA. """
|
||||||
|
|
||||||
|
def __init__(self, api_key):
|
||||||
|
# pylint: disable=no-name-in-module, unused-variable
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
self._api_key = api_key
|
||||||
|
self._data = {"apikey": self._api_key}
|
||||||
|
|
||||||
|
self.nma = Session()
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
title = kwargs.get(ATTR_TITLE)
|
||||||
|
|
||||||
|
self._data['application'] = 'home-assistant'
|
||||||
|
self._data['event'] = title
|
||||||
|
self._data['description'] = message
|
||||||
|
self._data['priority'] = 0
|
||||||
|
|
||||||
|
response = self.nma.get(_RESOURCE + 'notify',
|
||||||
|
params=self._data)
|
||||||
|
tree = ET.fromstring(response.content)
|
||||||
|
|
||||||
|
if tree[0].tag == 'error':
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to perform request. Error: %s", tree[0].text)
|
@ -1,5 +1,24 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.notify.pushbullet
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
PushBullet platform for notify component.
|
PushBullet platform for notify component.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the PushBullet notifier you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: pushbullet
|
||||||
|
api_key: YOUR_API_KEY
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
Enter the API key for PushBullet. Go to https://www.pushbullet.com/ to retrieve
|
||||||
|
your API key.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.notify.pushover
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Pushover platform for notify component.
|
Pushover platform for notify component.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -11,7 +14,7 @@ notify:
|
|||||||
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||||
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
api_key
|
api_key
|
||||||
*Required
|
*Required
|
||||||
|
127
homeassistant/components/notify/xmpp.py
Normal file
127
homeassistant/components/notify/xmpp.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.xmpp
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jabber (XMPP) notification service.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Jabber notifier you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
notify:
|
||||||
|
platform: xmpp
|
||||||
|
sender: YOUR_JID
|
||||||
|
password: YOUR_JABBER_ACCOUNT_PASSWORD
|
||||||
|
recipient: YOUR_RECIPIENT
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
sender
|
||||||
|
*Required
|
||||||
|
The Jabber ID (JID) that will act as origin of the messages. Add your JID
|
||||||
|
including the domain, e.g. your_name@jabber.org.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given Jabber account.
|
||||||
|
|
||||||
|
recipient
|
||||||
|
*Required
|
||||||
|
The Jabber ID (JID) that will receive the messages.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import sleekxmpp. "
|
||||||
|
"Did you maybe not install the 'SleekXMPP' package?")
|
||||||
|
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the Jabber (XMPP) notification service. """
|
||||||
|
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: ['sender',
|
||||||
|
'password',
|
||||||
|
'recipient']},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
SendNotificationBot(config[DOMAIN]['sender'] + '/home-assistant',
|
||||||
|
config[DOMAIN]['password'],
|
||||||
|
config[DOMAIN]['recipient'],
|
||||||
|
'')
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to contact jabber server."
|
||||||
|
"Please check your credentials.")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
return XmppNotificationService(config[DOMAIN]['sender'],
|
||||||
|
config[DOMAIN]['password'],
|
||||||
|
config[DOMAIN]['recipient'])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class XmppNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for Jabber (XMPP). """
|
||||||
|
|
||||||
|
def __init__(self, sender, password, recipient):
|
||||||
|
self._sender = sender
|
||||||
|
self._password = password
|
||||||
|
self._recipient = recipient
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
title = kwargs.get(ATTR_TITLE)
|
||||||
|
data = title + ": " + message
|
||||||
|
|
||||||
|
SendNotificationBot(self._sender + '/home-assistant',
|
||||||
|
self._password,
|
||||||
|
self._recipient,
|
||||||
|
data)
|
||||||
|
|
||||||
|
|
||||||
|
class SendNotificationBot(sleekxmpp.ClientXMPP):
|
||||||
|
""" Service for sending Jabber (XMPP) messages. """
|
||||||
|
|
||||||
|
def __init__(self, jid, password, recipient, msg):
|
||||||
|
|
||||||
|
super(SendNotificationBot, self).__init__(jid, password)
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.ERROR)
|
||||||
|
|
||||||
|
self.recipient = recipient
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
self.use_tls = True
|
||||||
|
self.use_ipv6 = False
|
||||||
|
self.add_event_handler('failed_auth', self.check_credentials)
|
||||||
|
self.add_event_handler('session_start', self.start)
|
||||||
|
self.connect()
|
||||||
|
self.process(block=False)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
""" Starts the communication and sends the message. """
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat')
|
||||||
|
self.disconnect(wait=True)
|
||||||
|
|
||||||
|
def check_credentials(self, event):
|
||||||
|
"""" Disconnect from the server if credentials are invalid. """
|
||||||
|
self.disconnect()
|
@ -10,11 +10,11 @@ import threading
|
|||||||
import queue
|
import queue
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
import time
|
|
||||||
import json
|
import json
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from homeassistant import Event, EventOrigin, State
|
from homeassistant import Event, EventOrigin, State
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
from homeassistant.remote import JSONEncoder
|
from homeassistant.remote import JSONEncoder
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||||
@ -60,8 +60,9 @@ def row_to_state(row):
|
|||||||
""" Convert a databsae row to a state. """
|
""" Convert a databsae row to a state. """
|
||||||
try:
|
try:
|
||||||
return State(
|
return State(
|
||||||
row[1], row[2], json.loads(row[3]), datetime.fromtimestamp(row[4]),
|
row[1], row[2], json.loads(row[3]),
|
||||||
datetime.fromtimestamp(row[5]))
|
date_util.utc_from_timestamp(row[4]),
|
||||||
|
date_util.utc_from_timestamp(row[5]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to state: %s", row)
|
_LOGGER.exception("Error converting row to state: %s", row)
|
||||||
@ -72,7 +73,7 @@ def row_to_event(row):
|
|||||||
""" Convert a databse row to an event. """
|
""" Convert a databse row to an event. """
|
||||||
try:
|
try:
|
||||||
return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()],
|
return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()],
|
||||||
datetime.fromtimestamp(row[5]))
|
date_util.utc_from_timestamp(row[5]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to event: %s", row)
|
_LOGGER.exception("Error converting row to event: %s", row)
|
||||||
@ -113,10 +114,10 @@ class RecorderRun(object):
|
|||||||
self.start = _INSTANCE.recording_start
|
self.start = _INSTANCE.recording_start
|
||||||
self.closed_incorrect = False
|
self.closed_incorrect = False
|
||||||
else:
|
else:
|
||||||
self.start = datetime.fromtimestamp(row[1])
|
self.start = date_util.utc_from_timestamp(row[1])
|
||||||
|
|
||||||
if row[2] is not None:
|
if row[2] is not None:
|
||||||
self.end = datetime.fromtimestamp(row[2])
|
self.end = date_util.utc_from_timestamp(row[2])
|
||||||
|
|
||||||
self.closed_incorrect = bool(row[3])
|
self.closed_incorrect = bool(row[3])
|
||||||
|
|
||||||
@ -166,7 +167,8 @@ class Recorder(threading.Thread):
|
|||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
self.quit_object = object()
|
self.quit_object = object()
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.recording_start = datetime.now()
|
self.recording_start = date_util.utcnow()
|
||||||
|
self.utc_offset = date_util.now().utcoffset().total_seconds()
|
||||||
|
|
||||||
def start_recording(event):
|
def start_recording(event):
|
||||||
""" Start recording. """
|
""" Start recording. """
|
||||||
@ -187,16 +189,21 @@ class Recorder(threading.Thread):
|
|||||||
if event == self.quit_object:
|
if event == self.quit_object:
|
||||||
self._close_run()
|
self._close_run()
|
||||||
self._close_connection()
|
self._close_connection()
|
||||||
|
self.queue.task_done()
|
||||||
return
|
return
|
||||||
|
|
||||||
elif event.event_type == EVENT_TIME_CHANGED:
|
elif event.event_type == EVENT_TIME_CHANGED:
|
||||||
|
self.queue.task_done()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif event.event_type == EVENT_STATE_CHANGED:
|
event_id = self.record_event(event)
|
||||||
self.record_state(
|
|
||||||
event.data['entity_id'], event.data.get('new_state'))
|
|
||||||
|
|
||||||
self.record_event(event)
|
if event.event_type == EVENT_STATE_CHANGED:
|
||||||
|
self.record_state(
|
||||||
|
event.data['entity_id'], event.data.get('new_state'),
|
||||||
|
event_id)
|
||||||
|
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
def event_listener(self, event):
|
def event_listener(self, event):
|
||||||
""" Listens for new events on the EventBus and puts them
|
""" Listens for new events on the EventBus and puts them
|
||||||
@ -207,33 +214,43 @@ class Recorder(threading.Thread):
|
|||||||
""" Tells the recorder to shut down. """
|
""" Tells the recorder to shut down. """
|
||||||
self.queue.put(self.quit_object)
|
self.queue.put(self.quit_object)
|
||||||
|
|
||||||
def record_state(self, entity_id, state):
|
def record_state(self, entity_id, state, event_id):
|
||||||
""" Save a state to the database. """
|
""" Save a state to the database. """
|
||||||
now = datetime.now()
|
now = date_util.utcnow()
|
||||||
|
|
||||||
|
# State got deleted
|
||||||
if state is None:
|
if state is None:
|
||||||
info = (entity_id, '', "{}", now, now, now)
|
state_state = ''
|
||||||
|
state_attr = '{}'
|
||||||
|
last_changed = last_updated = now
|
||||||
else:
|
else:
|
||||||
info = (
|
state_state = state.state
|
||||||
entity_id.lower(), state.state, json.dumps(state.attributes),
|
state_attr = json.dumps(state.attributes)
|
||||||
state.last_changed, state.last_updated, now)
|
last_changed = state.last_changed
|
||||||
|
last_updated = state.last_updated
|
||||||
|
|
||||||
|
info = (
|
||||||
|
entity_id, state_state, state_attr, last_changed, last_updated,
|
||||||
|
now, self.utc_offset, event_id)
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"INSERT INTO states ("
|
"INSERT INTO states ("
|
||||||
"entity_id, state, attributes, last_changed, last_updated,"
|
"entity_id, state, attributes, last_changed, last_updated,"
|
||||||
"created) VALUES (?, ?, ?, ?, ?, ?)", info)
|
"created, utc_offset, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
info)
|
||||||
|
|
||||||
def record_event(self, event):
|
def record_event(self, event):
|
||||||
""" Save an event to the database. """
|
""" Save an event to the database. """
|
||||||
info = (
|
info = (
|
||||||
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
||||||
str(event.origin), datetime.now(), event.time_fired,
|
str(event.origin), date_util.utcnow(), event.time_fired,
|
||||||
|
self.utc_offset
|
||||||
)
|
)
|
||||||
|
|
||||||
self.query(
|
return self.query(
|
||||||
"INSERT INTO events ("
|
"INSERT INTO events ("
|
||||||
"event_type, event_data, origin, created, time_fired"
|
"event_type, event_data, origin, created, time_fired, utc_offset"
|
||||||
") VALUES (?, ?, ?, ?, ?)", info)
|
") VALUES (?, ?, ?, ?, ?, ?)", info, RETURN_LASTROWID)
|
||||||
|
|
||||||
def query(self, sql_query, data=None, return_value=None):
|
def query(self, sql_query, data=None, return_value=None):
|
||||||
""" Query the database. """
|
""" Query the database. """
|
||||||
@ -262,6 +279,10 @@ class Recorder(threading.Thread):
|
|||||||
"Error querying the database using: %s", sql_query)
|
"Error querying the database using: %s", sql_query)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def block_till_done(self):
|
||||||
|
""" Blocks till all events processed. """
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
def _setup_connection(self):
|
def _setup_connection(self):
|
||||||
""" Ensure database is ready to fly. """
|
""" Ensure database is ready to fly. """
|
||||||
db_path = self.hass.config.path(DB_FILE)
|
db_path = self.hass.config.path(DB_FILE)
|
||||||
@ -282,7 +303,7 @@ class Recorder(threading.Thread):
|
|||||||
def save_migration(migration_id):
|
def save_migration(migration_id):
|
||||||
""" Save and commit a migration to the database. """
|
""" Save and commit a migration to the database. """
|
||||||
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
||||||
(migration_id, datetime.now()))
|
(migration_id, date_util.utcnow()))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||||
|
|
||||||
@ -297,7 +318,7 @@ class Recorder(threading.Thread):
|
|||||||
migration_id = 0
|
migration_id = 0
|
||||||
|
|
||||||
if migration_id < 1:
|
if migration_id < 1:
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE recorder_runs (
|
CREATE TABLE recorder_runs (
|
||||||
run_id integer primary key,
|
run_id integer primary key,
|
||||||
start integer,
|
start integer,
|
||||||
@ -306,7 +327,7 @@ class Recorder(threading.Thread):
|
|||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
event_id integer primary key,
|
event_id integer primary key,
|
||||||
event_type text,
|
event_type text,
|
||||||
@ -314,10 +335,10 @@ class Recorder(threading.Thread):
|
|||||||
origin text,
|
origin text,
|
||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
cur.execute(
|
self.query(
|
||||||
'CREATE INDEX events__event_type ON events(event_type)')
|
'CREATE INDEX events__event_type ON events(event_type)')
|
||||||
|
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE states (
|
CREATE TABLE states (
|
||||||
state_id integer primary key,
|
state_id integer primary key,
|
||||||
entity_id text,
|
entity_id text,
|
||||||
@ -327,20 +348,57 @@ class Recorder(threading.Thread):
|
|||||||
last_updated integer,
|
last_updated integer,
|
||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
cur.execute('CREATE INDEX states__entity_id ON states(entity_id)')
|
self.query('CREATE INDEX states__entity_id ON states(entity_id)')
|
||||||
|
|
||||||
save_migration(1)
|
save_migration(1)
|
||||||
|
|
||||||
if migration_id < 2:
|
if migration_id < 2:
|
||||||
cur.execute("""
|
self.query("""
|
||||||
ALTER TABLE events
|
ALTER TABLE events
|
||||||
ADD COLUMN time_fired integer
|
ADD COLUMN time_fired integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
cur.execute('UPDATE events SET time_fired=created')
|
self.query('UPDATE events SET time_fired=created')
|
||||||
|
|
||||||
save_migration(2)
|
save_migration(2)
|
||||||
|
|
||||||
|
if migration_id < 3:
|
||||||
|
utc_offset = self.utc_offset
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE recorder_runs
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE events
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE states
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
||||||
|
self.query("UPDATE events SET utc_offset=?", [utc_offset])
|
||||||
|
self.query("UPDATE states SET utc_offset=?", [utc_offset])
|
||||||
|
|
||||||
|
save_migration(3)
|
||||||
|
|
||||||
|
if migration_id < 4:
|
||||||
|
# We had a bug where we did not save utc offset for recorder runs
|
||||||
|
self.query(
|
||||||
|
"""UPDATE recorder_runs SET utc_offset=?
|
||||||
|
WHERE utc_offset IS NULL""", [self.utc_offset])
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE states
|
||||||
|
ADD COLUMN event_id integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
save_migration(4)
|
||||||
|
|
||||||
def _close_connection(self):
|
def _close_connection(self):
|
||||||
""" Close connection to the database. """
|
""" Close connection to the database. """
|
||||||
_LOGGER.info("Closing database")
|
_LOGGER.info("Closing database")
|
||||||
@ -356,19 +414,20 @@ class Recorder(threading.Thread):
|
|||||||
_LOGGER.warning("Found unfinished sessions")
|
_LOGGER.warning("Found unfinished sessions")
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"INSERT INTO recorder_runs (start, created) VALUES (?, ?)",
|
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
||||||
(self.recording_start, datetime.now()))
|
VALUES (?, ?, ?)""",
|
||||||
|
(self.recording_start, date_util.utcnow(), self.utc_offset))
|
||||||
|
|
||||||
def _close_run(self):
|
def _close_run(self):
|
||||||
""" Save end time for current run. """
|
""" Save end time for current run. """
|
||||||
self.query(
|
self.query(
|
||||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||||
(datetime.now(), self.recording_start))
|
(date_util.utcnow(), self.recording_start))
|
||||||
|
|
||||||
|
|
||||||
def _adapt_datetime(datetimestamp):
|
def _adapt_datetime(datetimestamp):
|
||||||
""" Turn a datetime into an integer for in the DB. """
|
""" Turn a datetime into an integer for in the DB. """
|
||||||
return time.mktime(datetimestamp.timetuple())
|
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||||
|
|
||||||
|
|
||||||
def _verify_instance():
|
def _verify_instance():
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.scheduler
|
homeassistant.components.scheduler
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
A component that will act as a scheduler and performe actions based
|
A component that will act as a scheduler and perform actions based
|
||||||
on the events in the schedule.
|
on the events in the schedule.
|
||||||
|
|
||||||
It will read a json object from schedule.json in the config dir
|
It will read a json object from schedule.json in the config dir
|
||||||
and create a schedule based on it.
|
and create a schedule based on it.
|
||||||
Each schedule is a JSON with the keys id, name, description,
|
Each schedule is a JSON with the keys id, name, description,
|
||||||
entity_ids, and events.
|
entity_ids, and events.
|
||||||
- days is an array with the weekday number (monday=0) that the schdule
|
- days is an array with the weekday number (monday=0) that the schedule
|
||||||
is active
|
is active
|
||||||
- entity_ids an array with entity ids that the events in the schedule should
|
- entity_ids an array with entity ids that the events in the schedule should
|
||||||
effect (can also be groups)
|
effect (can also be groups)
|
||||||
- events is an array of objects that describe the different events that is
|
- events is an array of objects that describe the different events that is
|
||||||
supported. Read in the events descriptions for more information
|
supported. Read in the events descriptions for more information.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
@ -22,7 +22,6 @@ from homeassistant import bootstrap
|
|||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
|
|
||||||
# The domain of your component. Should be equal to the name of your component
|
|
||||||
DOMAIN = 'scheduler'
|
DOMAIN = 'scheduler'
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -33,10 +32,10 @@ _SCHEDULE_FILE = 'schedule.json'
|
|||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Create the schedules """
|
""" Create the schedules. """
|
||||||
|
|
||||||
def setup_listener(schedule, event_data):
|
def setup_listener(schedule, event_data):
|
||||||
""" Creates the event listener based on event_data """
|
""" Creates the event listener based on event_data. """
|
||||||
event_type = event_data['type']
|
event_type = event_data['type']
|
||||||
component = event_type
|
component = event_type
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ def setup(hass, config):
|
|||||||
event_data)
|
event_data)
|
||||||
|
|
||||||
def setup_schedule(schedule_data):
|
def setup_schedule(schedule_data):
|
||||||
""" setup a schedule based on the description """
|
""" Setup a schedule based on the description. """
|
||||||
|
|
||||||
schedule = Schedule(schedule_data['id'],
|
schedule = Schedule(schedule_data['id'],
|
||||||
name=schedule_data['name'],
|
name=schedule_data['name'],
|
||||||
@ -97,17 +96,17 @@ class Schedule(object):
|
|||||||
self.__event_listeners = []
|
self.__event_listeners = []
|
||||||
|
|
||||||
def add_event_listener(self, event_listener):
|
def add_event_listener(self, event_listener):
|
||||||
""" Add a event to the schedule """
|
""" Add a event to the schedule. """
|
||||||
self.__event_listeners.append(event_listener)
|
self.__event_listeners.append(event_listener)
|
||||||
|
|
||||||
def schedule(self, hass):
|
def schedule(self, hass):
|
||||||
""" Schedule all the events in the schdule """
|
""" Schedule all the events in the schedule. """
|
||||||
for event in self.__event_listeners:
|
for event in self.__event_listeners:
|
||||||
event.schedule(hass)
|
event.schedule(hass)
|
||||||
|
|
||||||
|
|
||||||
class EventListener(object):
|
class EventListener(object):
|
||||||
""" The base EventListner class that the schedule uses """
|
""" The base EventListener class that the schedule uses. """
|
||||||
def __init__(self, schedule):
|
def __init__(self, schedule):
|
||||||
self.my_schedule = schedule
|
self.my_schedule = schedule
|
||||||
|
|
||||||
@ -122,7 +121,7 @@ class EventListener(object):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class ServiceEventListener(EventListener):
|
class ServiceEventListener(EventListener):
|
||||||
""" A EventListner that calls a service when executed """
|
""" A EventListener that calls a service when executed. """
|
||||||
|
|
||||||
def __init__(self, schdule, service):
|
def __init__(self, schdule, service):
|
||||||
EventListener.__init__(self, schdule)
|
EventListener.__init__(self, schdule)
|
||||||
@ -130,7 +129,7 @@ class ServiceEventListener(EventListener):
|
|||||||
(self.domain, self.service) = service.split('.')
|
(self.domain, self.service) = service.split('.')
|
||||||
|
|
||||||
def execute(self, hass):
|
def execute(self, hass):
|
||||||
""" Call the service """
|
""" Call the service. """
|
||||||
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
||||||
hass.call_service(self.domain, self.service, data)
|
hass.call_service(self.domain, self.service, data)
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.scheduler.time
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
An event in the scheduler component that will call the service
|
An event in the scheduler component that will call the service
|
||||||
every specified day at the time specified.
|
every specified day at the time specified.
|
||||||
A time event need to have the type 'time', which service to call and at
|
A time event need to have the type 'time', which service to call and at
|
||||||
@ -11,17 +13,17 @@ which time.
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_event_listener(schedule, event_listener_data):
|
def create_event_listener(schedule, event_listener_data):
|
||||||
""" Create a TimeEvent based on the description """
|
""" Create a TimeEvent based on the description. """
|
||||||
|
|
||||||
service = event_listener_data['service']
|
service = event_listener_data['service']
|
||||||
(hour, minute, second) = [int(x) for x in
|
(hour, minute, second) = [int(x) for x in
|
||||||
@ -32,7 +34,7 @@ def create_event_listener(schedule, event_listener_data):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class TimeEventListener(ServiceEventListener):
|
class TimeEventListener(ServiceEventListener):
|
||||||
""" The time event that the scheduler uses """
|
""" The time event that the scheduler uses. """
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, schedule, service, hour, minute, second):
|
def __init__(self, schedule, service, hour, minute, second):
|
||||||
@ -43,16 +45,14 @@ class TimeEventListener(ServiceEventListener):
|
|||||||
self.second = second
|
self.second = second
|
||||||
|
|
||||||
def schedule(self, hass):
|
def schedule(self, hass):
|
||||||
""" Schedule this event so that it will be called """
|
""" Schedule this event so that it will be called. """
|
||||||
|
|
||||||
next_time = datetime.now().replace(hour=self.hour,
|
next_time = dt_util.now().replace(
|
||||||
minute=self.minute,
|
hour=self.hour, minute=self.minute, second=self.second)
|
||||||
second=self.second,
|
|
||||||
microsecond=0)
|
|
||||||
|
|
||||||
# Calculate the next time the event should be executed.
|
# Calculate the next time the event should be executed.
|
||||||
# That is the next day that the schedule is configured to run
|
# That is the next day that the schedule is configured to run
|
||||||
while next_time < datetime.now() or \
|
while next_time < dt_util.now() or \
|
||||||
next_time.weekday() not in self.my_schedule.days:
|
next_time.weekday() not in self.my_schedule.days:
|
||||||
|
|
||||||
next_time = next_time + timedelta(days=1)
|
next_time = next_time + timedelta(days=1)
|
||||||
|
@ -6,7 +6,8 @@ Scripts are a sequence of actions that can be triggered manually
|
|||||||
by the user or automatically based upon automation events, etc.
|
by the user or automatically based upon automation events, etc.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from homeassistant.util import split_entity_id
|
from homeassistant.util import split_entity_id
|
||||||
@ -109,7 +110,7 @@ class Script(object):
|
|||||||
self._call_service(action)
|
self._call_service(action)
|
||||||
elif CONF_DELAY in action:
|
elif CONF_DELAY in action:
|
||||||
delay = timedelta(**action[CONF_DELAY])
|
delay = timedelta(**action[CONF_DELAY])
|
||||||
point_in_time = datetime.now() + delay
|
point_in_time = date_util.now() + delay
|
||||||
self.listener = self.hass.track_point_in_time(
|
self.listener = self.hass.track_point_in_time(
|
||||||
self, point_in_time)
|
self, point_in_time)
|
||||||
return False
|
return False
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
""" Support for Wink sensors. """
|
"""
|
||||||
|
homeassistant.components.sensor.demo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Demo platform that has two fake sensors.
|
||||||
|
|
||||||
|
"""
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
|
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
""" Support for ISY994 sensors. """
|
"""
|
||||||
# system imports
|
homeassistant.components.sensor.isy994
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for ISY994 sensors.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# homeassistant imports
|
|
||||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||||
HIDDEN_STRING)
|
HIDDEN_STRING)
|
||||||
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME,
|
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME,
|
||||||
@ -24,7 +27,7 @@ DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like',
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the isy994 platform. """
|
""" Sets up the ISY994 platform. """
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
devs = []
|
devs = []
|
||||||
@ -81,7 +84,7 @@ class WeatherPseudoNode(object):
|
|||||||
|
|
||||||
|
|
||||||
class ISYSensorDevice(ISYDeviceABC):
|
class ISYSensorDevice(ISYDeviceABC):
|
||||||
""" represents a isy sensor within home assistant. """
|
""" Represents a ISY sensor. """
|
||||||
|
|
||||||
_domain = 'sensor'
|
_domain = 'sensor'
|
||||||
|
|
||||||
|
158
homeassistant/components/sensor/mysensors.py
Normal file
158
homeassistant/components/sensor/mysensors.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.mysensors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for MySensors sensors.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the MySensors sensor you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: mysensors
|
||||||
|
port: '/dev/ttyACM0'
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
port
|
||||||
|
*Required
|
||||||
|
Port of your connection to your MySensors device.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
import homeassistant.external.pymysensors.mysensors.mysensors as mysensors
|
||||||
|
import homeassistant.external.pymysensors.mysensors.const as const
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP,
|
||||||
|
TEMP_CELCIUS, TEMP_FAHRENHEIT,
|
||||||
|
STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
|
CONF_PORT = "port"
|
||||||
|
CONF_DEBUG = "debug"
|
||||||
|
CONF_PERSISTENCE = "persistence"
|
||||||
|
|
||||||
|
ATTR_NODE_ID = "node_id"
|
||||||
|
ATTR_CHILD_ID = "child_id"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Setup the mysensors platform. """
|
||||||
|
|
||||||
|
devices = {} # keep track of devices added to HA
|
||||||
|
# Just assume celcius means that the user wants metric for now.
|
||||||
|
# It may make more sense to make this a global config option in the future.
|
||||||
|
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
|
||||||
|
|
||||||
|
def sensor_update(update_type, nid):
|
||||||
|
""" Callback for sensor updates from the MySensors gateway. """
|
||||||
|
_LOGGER.info("sensor_update %s: node %s", update_type, nid)
|
||||||
|
sensor = gateway.sensors[nid]
|
||||||
|
if sensor.sketch_name is None:
|
||||||
|
return
|
||||||
|
if nid not in devices:
|
||||||
|
devices[nid] = {}
|
||||||
|
|
||||||
|
node = devices[nid]
|
||||||
|
new_devices = []
|
||||||
|
for child_id, child in sensor.children.items():
|
||||||
|
if child_id not in node:
|
||||||
|
node[child_id] = {}
|
||||||
|
for value_type, value in child.values.items():
|
||||||
|
if value_type not in node[child_id]:
|
||||||
|
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
|
||||||
|
node[child_id][value_type] = \
|
||||||
|
MySensorsNodeValue(
|
||||||
|
nid, child_id, name, value_type, is_metric)
|
||||||
|
new_devices.append(node[child_id][value_type])
|
||||||
|
else:
|
||||||
|
node[child_id][value_type].update_sensor(
|
||||||
|
value, sensor.battery_level)
|
||||||
|
|
||||||
|
if new_devices:
|
||||||
|
_LOGGER.info("adding new devices: %s", new_devices)
|
||||||
|
add_devices(new_devices)
|
||||||
|
|
||||||
|
port = config.get(CONF_PORT)
|
||||||
|
if port is None:
|
||||||
|
_LOGGER.error("Missing required key 'port'")
|
||||||
|
return False
|
||||||
|
|
||||||
|
persistence = config.get(CONF_PERSISTENCE, True)
|
||||||
|
|
||||||
|
gateway = mysensors.SerialGateway(port, sensor_update,
|
||||||
|
persistence=persistence)
|
||||||
|
gateway.metric = is_metric
|
||||||
|
gateway.debug = config.get(CONF_DEBUG, False)
|
||||||
|
gateway.start()
|
||||||
|
|
||||||
|
if persistence:
|
||||||
|
for nid in gateway.sensors:
|
||||||
|
sensor_update('sensor_update', nid)
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||||
|
lambda event: gateway.stop())
|
||||||
|
|
||||||
|
|
||||||
|
class MySensorsNodeValue(Entity):
|
||||||
|
""" Represents the value of a MySensors child node. """
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self, node_id, child_id, name, value_type, metric):
|
||||||
|
self._name = name
|
||||||
|
self.node_id = node_id
|
||||||
|
self.child_id = child_id
|
||||||
|
self.battery_level = 0
|
||||||
|
self.value_type = value_type
|
||||||
|
self.metric = metric
|
||||||
|
self._value = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" MySensor gateway pushes its state to HA. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of this sensor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity. """
|
||||||
|
if self.value_type == const.SetReq.V_TEMP:
|
||||||
|
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
|
||||||
|
elif self.value_type == const.SetReq.V_HUM or \
|
||||||
|
self.value_type == const.SetReq.V_DIMMER or \
|
||||||
|
self.value_type == const.SetReq.V_LIGHT_LEVEL:
|
||||||
|
return '%'
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
""" Returns the state attributes. """
|
||||||
|
return {
|
||||||
|
ATTR_NODE_ID: self.node_id,
|
||||||
|
ATTR_CHILD_ID: self.child_id,
|
||||||
|
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||||
|
}
|
||||||
|
|
||||||
|
def update_sensor(self, value, battery_level):
|
||||||
|
""" Update a sensor with the latest value from the controller. """
|
||||||
|
_LOGGER.info("%s value = %s", self._name, value)
|
||||||
|
if self.value_type == const.SetReq.V_TRIPPED or \
|
||||||
|
self.value_type == const.SetReq.V_ARMED:
|
||||||
|
self._value = STATE_ON if int(value) == 1 else STATE_OFF
|
||||||
|
else:
|
||||||
|
self._value = value
|
||||||
|
self.battery_level = battery_level
|
||||||
|
self.update_ha_state()
|
174
homeassistant/components/sensor/openweathermap.py
Normal file
174
homeassistant/components/sensor/openweathermap.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.openweathermap
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
OpenWeatherMap (OWM) service.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the OpenWeatherMap sensor you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: openweathermap
|
||||||
|
api_key: YOUR_APP_KEY
|
||||||
|
monitored_variables:
|
||||||
|
- type: 'weather'
|
||||||
|
- type: 'temperature'
|
||||||
|
- type: 'wind_speed'
|
||||||
|
- type: 'humidity'
|
||||||
|
- type: 'pressure'
|
||||||
|
- type: 'clouds'
|
||||||
|
- type: 'rain'
|
||||||
|
- type: 'snow'
|
||||||
|
|
||||||
|
VARIABLES:
|
||||||
|
|
||||||
|
api_key
|
||||||
|
*Required
|
||||||
|
To retrieve this value log into your account at http://openweathermap.org/
|
||||||
|
|
||||||
|
monitored_variables
|
||||||
|
*Required
|
||||||
|
An array specifying the variables to monitor.
|
||||||
|
|
||||||
|
These are the variables for the monitored_variables array:
|
||||||
|
|
||||||
|
type
|
||||||
|
*Required
|
||||||
|
The variable you wish to monitor, see the configuration example above for a
|
||||||
|
list of all available variables
|
||||||
|
|
||||||
|
Details for the API : http://bugs.openweathermap.org/projects/api/wiki
|
||||||
|
|
||||||
|
Only metric measurements are supported at the moment.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_THROTTLED_REFRESH = None
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
'weather': ['Condition', ''],
|
||||||
|
'temperature': ['Temperature', ''],
|
||||||
|
'wind_speed': ['Wind speed', 'm/s'],
|
||||||
|
'humidity': ['Humidity', '%'],
|
||||||
|
'pressure': ['Pressure', 'hPa'],
|
||||||
|
'clouds': ['Cloud coverage', '%'],
|
||||||
|
'rain': ['Rain', 'mm'],
|
||||||
|
'snow': ['Snow', 'mm']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Get the OpenWeatherMap sensor. """
|
||||||
|
|
||||||
|
if None in (hass.config.latitude, hass.config.longitude):
|
||||||
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyowm import OWM
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import pyowm. "
|
||||||
|
"Did you maybe not install the 'PyOWM' package?")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
||||||
|
unit = hass.config.temperature_unit
|
||||||
|
owm = OWM(config.get(CONF_API_KEY, None))
|
||||||
|
obs = owm.weather_at_coords(hass.config.latitude, hass.config.longitude)
|
||||||
|
|
||||||
|
if not owm:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Connection error "
|
||||||
|
"Please check your settings for OpenWeatherMap.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
dev = []
|
||||||
|
for variable in config['monitored_variables']:
|
||||||
|
if variable['type'] not in SENSOR_TYPES:
|
||||||
|
_LOGGER.error('Sensor type: "%s" does not exist', variable['type'])
|
||||||
|
else:
|
||||||
|
dev.append(OpenWeatherMapSensor(variable['type'], obs, unit))
|
||||||
|
|
||||||
|
add_devices(dev)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class OpenWeatherMapSensor(Entity):
|
||||||
|
""" Implements an OpenWeatherMap sensor. """
|
||||||
|
|
||||||
|
def __init__(self, sensor_type, weather_data, unit):
|
||||||
|
self.client_name = 'Weather - '
|
||||||
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
|
self.owa_client = weather_data
|
||||||
|
self._unit = unit
|
||||||
|
self.type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.client_name + ' ' + self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity, if any. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data from OWM and updates the states. """
|
||||||
|
data = self.owa_client.get_weather()
|
||||||
|
|
||||||
|
if self.type == 'weather':
|
||||||
|
self._state = data.get_detailed_status()
|
||||||
|
|
||||||
|
if self.type == 'temperature':
|
||||||
|
if self._unit == TEMP_CELCIUS:
|
||||||
|
self._state = round(data.get_temperature('celsius')['temp'],
|
||||||
|
1)
|
||||||
|
elif self._unit == TEMP_FAHRENHEIT:
|
||||||
|
self._state = round(data.get_temperature('fahrenheit')['temp'],
|
||||||
|
1)
|
||||||
|
else:
|
||||||
|
self._state = round(data.get_temperature()['temp'], 1)
|
||||||
|
|
||||||
|
elif self.type == 'wind_speed':
|
||||||
|
self._state = data.get_wind()['speed']
|
||||||
|
|
||||||
|
elif self.type == 'humidity':
|
||||||
|
self._state = data.get_humidity()
|
||||||
|
|
||||||
|
elif self.type == 'pressure':
|
||||||
|
self._state = round(data.get_pressure()['press'], 0)
|
||||||
|
|
||||||
|
elif self.type == 'clouds':
|
||||||
|
self._state = data.get_clouds()
|
||||||
|
|
||||||
|
elif self.type == 'rain':
|
||||||
|
if data.get_rain():
|
||||||
|
self._state = round(data.get_rain()['3h'], 0)
|
||||||
|
else:
|
||||||
|
self._state = 'not raining'
|
||||||
|
self._unit_of_measurement = ''
|
||||||
|
|
||||||
|
elif self.type == 'snow':
|
||||||
|
if data.get_snow():
|
||||||
|
self._state = round(data.get_snow(), 0)
|
||||||
|
else:
|
||||||
|
self._state = 'not snowing'
|
||||||
|
self._unit_of_measurement = ''
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.sabnzbd
|
homeassistant.components.sensor.sabnzbd
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Monitors SABnzbd NZB client API
|
Monitors SABnzbd NZB client API
|
||||||
|
|
||||||
@ -22,14 +22,12 @@ sensor:
|
|||||||
- type: 'disk_size'
|
- type: 'disk_size'
|
||||||
- type: 'disk_free'
|
- type: 'disk_free'
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
base_url
|
base_url
|
||||||
*Required
|
*Required
|
||||||
This is the base URL of your SABnzbd instance including the port number if not
|
This is the base URL of your SABnzbd instance including the port number if not
|
||||||
running on 80
|
running on 80. Example: http://192.168.1.32:8124/
|
||||||
Example: http://192.168.1.32:8124/
|
|
||||||
|
|
||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
@ -44,9 +42,7 @@ These are the variables for the monitored_variables array:
|
|||||||
type
|
type
|
||||||
*Required
|
*Required
|
||||||
The variable you wish to monitor, see the configuration example above for a
|
The variable you wish to monitor, see the configuration example above for a
|
||||||
list of all available variables
|
list of all available variables.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
@ -75,7 +71,7 @@ _THROTTLED_REFRESH = None
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the sensors """
|
""" Sets up the sensors. """
|
||||||
api_key = config.get("api_key")
|
api_key = config.get("api_key")
|
||||||
base_url = config.get("base_url")
|
base_url = config.get("base_url")
|
||||||
name = config.get("name", "SABnzbd")
|
name = config.get("name", "SABnzbd")
|
||||||
|
@ -1,9 +1,46 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.systemmonitor
|
homeassistant.components.sensor.systemmonitor
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Shows system monitor values such as: disk, memory and processor use
|
Shows system monitor values such as: disk, memory and processor use
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the System monitor sensor you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: systemmonitor
|
||||||
|
resources:
|
||||||
|
- type: 'disk_use_percent'
|
||||||
|
arg: '/'
|
||||||
|
- type: 'disk_use'
|
||||||
|
arg: '/home'
|
||||||
|
- type: 'disk_free'
|
||||||
|
arg: '/'
|
||||||
|
- type: 'memory_use_percent'
|
||||||
|
- type: 'memory_use'
|
||||||
|
- type: 'memory_free'
|
||||||
|
- type: 'processor_use'
|
||||||
|
- type: 'process'
|
||||||
|
arg: 'octave-cli'
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
resources
|
||||||
|
*Required
|
||||||
|
An array specifying the variables to monitor.
|
||||||
|
|
||||||
|
These are the variables for the resources array:
|
||||||
|
|
||||||
|
type
|
||||||
|
*Required
|
||||||
|
The variable you wish to monitor, see the configuration example above for a
|
||||||
|
sample list of variables.
|
||||||
|
|
||||||
|
arg
|
||||||
|
*Optional
|
||||||
|
Additional details for the type, eg. path, binary name, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@ -28,7 +65,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the sensors """
|
""" Sets up the sensors. """
|
||||||
|
|
||||||
dev = []
|
dev = []
|
||||||
for resource in config['resources']:
|
for resource in config['resources']:
|
||||||
@ -43,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class SystemMonitorSensor(Entity):
|
class SystemMonitorSensor(Entity):
|
||||||
""" A system monitor sensor """
|
""" A system monitor sensor. """
|
||||||
|
|
||||||
def __init__(self, sensor_type, argument=''):
|
def __init__(self, sensor_type, argument=''):
|
||||||
self._name = SENSOR_TYPES[sensor_type][0] + ' ' + argument
|
self._name = SENSOR_TYPES[sensor_type][0] + ' ' + argument
|
||||||
|
112
homeassistant/components/sensor/time_date.py
Normal file
112
homeassistant/components/sensor/time_date.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.time_date
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Date and Time service.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Date and Time sensor you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: time_date
|
||||||
|
display_options:
|
||||||
|
- type: 'time'
|
||||||
|
- type: 'date'
|
||||||
|
- type: 'date_time'
|
||||||
|
- type: 'time_date'
|
||||||
|
- type: 'time_utc'
|
||||||
|
- type: 'beat'
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
display_options
|
||||||
|
*Required
|
||||||
|
An array specifying the variables to display.
|
||||||
|
|
||||||
|
These are the variables for the display_options array.:
|
||||||
|
|
||||||
|
type
|
||||||
|
*Required
|
||||||
|
The variable you wish to display, see the configuration example above for a
|
||||||
|
list of all available variables.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
OPTION_TYPES = {
|
||||||
|
'time': 'Time',
|
||||||
|
'date': 'Date',
|
||||||
|
'date_time': 'Date & Time',
|
||||||
|
'time_date': 'Time & Date',
|
||||||
|
'beat': 'Time (beat)',
|
||||||
|
'time_utc': 'Time (UTC)',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Get the Time and Date sensor. """
|
||||||
|
|
||||||
|
if hass.config.time_zone is None:
|
||||||
|
_LOGGER.error("Timezone is not set in Home Assistant config")
|
||||||
|
return False
|
||||||
|
|
||||||
|
dev = []
|
||||||
|
for variable in config['display_options']:
|
||||||
|
if variable['type'] not in OPTION_TYPES:
|
||||||
|
_LOGGER.error('Option type: "%s" does not exist', variable['type'])
|
||||||
|
else:
|
||||||
|
dev.append(TimeDateSensor(variable['type']))
|
||||||
|
|
||||||
|
add_devices(dev)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class TimeDateSensor(Entity):
|
||||||
|
""" Implements a Time and Date sensor. """
|
||||||
|
|
||||||
|
def __init__(self, option_type):
|
||||||
|
self._name = OPTION_TYPES[option_type]
|
||||||
|
self.type = option_type
|
||||||
|
self._state = None
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data and updates the states. """
|
||||||
|
|
||||||
|
time_date = dt_util.utcnow()
|
||||||
|
time = dt_util.datetime_to_short_time_str(dt_util.as_local(time_date))
|
||||||
|
time_utc = dt_util.datetime_to_short_time_str(time_date)
|
||||||
|
date = dt_util.datetime_to_short_date_str(dt_util.as_local(time_date))
|
||||||
|
|
||||||
|
# Calculate the beat (Swatch Internet Time) time without date.
|
||||||
|
hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':')
|
||||||
|
beat = ((int(seconds) + (int(minutes) * 60) + ((int(hours) + 1) *
|
||||||
|
3600)) / 86.4)
|
||||||
|
|
||||||
|
if self.type == 'time':
|
||||||
|
self._state = time
|
||||||
|
elif self.type == 'date':
|
||||||
|
self._state = date
|
||||||
|
elif self.type == 'date_time':
|
||||||
|
self._state = date + ', ' + time
|
||||||
|
elif self.type == 'time_date':
|
||||||
|
self._state = time + ', ' + date
|
||||||
|
elif self.type == 'time_utc':
|
||||||
|
self._state = time_utc
|
||||||
|
elif self.type == 'beat':
|
||||||
|
self._state = '{0:.2f}'.format(beat)
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.sensor.transmission
|
homeassistant.components.sensor.transmission
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Monitors Transmission BitTorrent client API
|
Monitors Transmission BitTorrent client API
|
||||||
|
|
||||||
@ -21,17 +21,15 @@ sensor:
|
|||||||
- type: 'download_speed'
|
- type: 'download_speed'
|
||||||
- type: 'upload_speed'
|
- type: 'upload_speed'
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
host
|
host
|
||||||
*Required
|
*Required
|
||||||
This is the IP address of your Transmission Daemon
|
This is the IP address of your Transmission daemon. Example: 192.168.1.32
|
||||||
Example: 192.168.1.32
|
|
||||||
|
|
||||||
port
|
port
|
||||||
*Optional
|
*Optional
|
||||||
The port your Transmission daemon uses, defaults to 9091
|
The port your Transmission daemon uses, defaults to 9091. Example: 8080
|
||||||
Example: 8080
|
|
||||||
|
|
||||||
username
|
username
|
||||||
*Required
|
*Required
|
||||||
@ -43,7 +41,7 @@ Your Transmission password
|
|||||||
|
|
||||||
name
|
name
|
||||||
*Optional
|
*Optional
|
||||||
The name to use when displaying this Transmission instance
|
The name to use when displaying this Transmission instance.
|
||||||
|
|
||||||
monitored_variables
|
monitored_variables
|
||||||
*Required
|
*Required
|
||||||
@ -54,9 +52,7 @@ These are the variables for the monitored_variables array:
|
|||||||
type
|
type
|
||||||
*Required
|
*Required
|
||||||
The variable you wish to monitor, see the configuration example above for a
|
The variable you wish to monitor, see the configuration example above for a
|
||||||
list of all available variables
|
list of all available variables.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
@ -84,7 +80,7 @@ _THROTTLED_REFRESH = None
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the sensors """
|
""" Sets up the sensors. """
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
username = config.get(CONF_USERNAME, None)
|
username = config.get(CONF_USERNAME, None)
|
||||||
password = config.get(CONF_PASSWORD, None)
|
password = config.get(CONF_PASSWORD, None)
|
||||||
@ -123,7 +119,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class TransmissionSensor(Entity):
|
class TransmissionSensor(Entity):
|
||||||
""" A Transmission sensor """
|
""" A Transmission sensor. """
|
||||||
|
|
||||||
def __init__(self, sensor_type, transmission_client, client_name):
|
def __init__(self, sensor_type, transmission_client, client_name):
|
||||||
self._name = SENSOR_TYPES[sensor_type][0]
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
@ -158,7 +154,7 @@ class TransmissionSensor(Entity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest from Transmission and updates the state. """
|
""" Gets the latest data from Transmission and updates the state. """
|
||||||
self.refresh_transmission_data()
|
self.refresh_transmission_data()
|
||||||
if self.type == 'current_status':
|
if self.type == 'current_status':
|
||||||
if self.transmission_client.session:
|
if self.transmission_client.session:
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.sensor.vera
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Support for Vera sensors.
|
Support for Vera sensors.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
To use the Vera sensors you will need to add something like the following to
|
To use the Vera sensors you will need to add something like the following to
|
||||||
your config/configuration.yaml
|
your config/configuration.yaml
|
||||||
|
|
||||||
@ -15,7 +19,7 @@ sensor:
|
|||||||
13:
|
13:
|
||||||
name: Another sensor
|
name: Another sensor
|
||||||
|
|
||||||
VARIABLES:
|
Variables:
|
||||||
|
|
||||||
vera_controller_url
|
vera_controller_url
|
||||||
*Required
|
*Required
|
||||||
@ -96,12 +100,12 @@ def get_devices(hass, config):
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Performs setup for Vera controller devices """
|
""" Performs setup for Vera controller devices. """
|
||||||
add_devices(get_devices(hass, config))
|
add_devices(get_devices(hass, config))
|
||||||
|
|
||||||
|
|
||||||
class VeraSensor(Entity):
|
class VeraSensor(Entity):
|
||||||
""" Represents a Vera Sensor """
|
""" Represents a Vera Sensor. """
|
||||||
|
|
||||||
def __init__(self, vera_device, extra_data=None):
|
def __init__(self, vera_device, extra_data=None):
|
||||||
self.vera_device = vera_device
|
self.vera_device = vera_device
|
||||||
|
@ -20,7 +20,7 @@ DEPENDENCIES = ['group', 'device_tracker', 'light']
|
|||||||
CONF_KNOWN_LIGHT = "known_light"
|
CONF_KNOWN_LIGHT = "known_light"
|
||||||
|
|
||||||
# Attribute to tell which light has to flash whem an unknown person comes home
|
# Attribute to tell which light has to flash whem an unknown person comes home
|
||||||
# If ommitted will flash all.
|
# If omitted will flash all.
|
||||||
CONF_UNKNOWN_LIGHT = "unknown_light"
|
CONF_UNKNOWN_LIGHT = "unknown_light"
|
||||||
|
|
||||||
# Services to test the alarms
|
# Services to test the alarms
|
||||||
|
@ -22,10 +22,16 @@ which event (sunset or sunrise) and the offset.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.util import str_to_datetime, datetime_to_str
|
try:
|
||||||
|
import ephem
|
||||||
|
except ImportError:
|
||||||
|
# Error will be raised during setup
|
||||||
|
ephem = None
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -49,13 +55,21 @@ def is_on(hass, entity_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def next_setting(hass, entity_id=None):
|
def next_setting(hass, entity_id=None):
|
||||||
""" Returns the datetime object representing the next sun setting. """
|
""" Returns the local datetime object of the next sun setting. """
|
||||||
|
utc_next = next_setting_utc(hass, entity_id)
|
||||||
|
|
||||||
|
return dt_util.as_local(utc_next) if utc_next else None
|
||||||
|
|
||||||
|
|
||||||
|
def next_setting_utc(hass, entity_id=None):
|
||||||
|
""" Returns the UTC datetime object of the next sun setting. """
|
||||||
entity_id = entity_id or ENTITY_ID
|
entity_id = entity_id or ENTITY_ID
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_SETTING])
|
return dt_util.str_to_datetime(
|
||||||
|
state.attributes[STATE_ATTR_NEXT_SETTING])
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
# AttributeError if state is None
|
# AttributeError if state is None
|
||||||
# KeyError if STATE_ATTR_NEXT_SETTING does not exist
|
# KeyError if STATE_ATTR_NEXT_SETTING does not exist
|
||||||
@ -63,13 +77,21 @@ def next_setting(hass, entity_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def next_rising(hass, entity_id=None):
|
def next_rising(hass, entity_id=None):
|
||||||
""" Returns the datetime object representing the next sun rising. """
|
""" Returns the local datetime object of the next sun rising. """
|
||||||
|
utc_next = next_rising_utc(hass, entity_id)
|
||||||
|
|
||||||
|
return dt_util.as_local(utc_next) if utc_next else None
|
||||||
|
|
||||||
|
|
||||||
|
def next_rising_utc(hass, entity_id=None):
|
||||||
|
""" Returns the UTC datetime object of the next sun rising. """
|
||||||
entity_id = entity_id or ENTITY_ID
|
entity_id = entity_id or ENTITY_ID
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_RISING])
|
return dt_util.str_to_datetime(
|
||||||
|
state.attributes[STATE_ATTR_NEXT_RISING])
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
# AttributeError if state is None
|
# AttributeError if state is None
|
||||||
# KeyError if STATE_ATTR_NEXT_RISING does not exist
|
# KeyError if STATE_ATTR_NEXT_RISING does not exist
|
||||||
@ -80,78 +102,94 @@ def setup(hass, config):
|
|||||||
""" Tracks the state of the sun. """
|
""" Tracks the state of the sun. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
if ephem is None:
|
||||||
import ephem
|
|
||||||
except ImportError:
|
|
||||||
logger.exception("Error while importing dependency ephem.")
|
logger.exception("Error while importing dependency ephem.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sun = ephem.Sun() # pylint: disable=no-member
|
if None in (hass.config.latitude, hass.config.longitude):
|
||||||
|
logger.error("Latitude or longitude not set in Home Assistant config")
|
||||||
latitude = str(hass.config.latitude)
|
|
||||||
longitude = str(hass.config.longitude)
|
|
||||||
|
|
||||||
# Validate latitude and longitude
|
|
||||||
observer = ephem.Observer()
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
observer.lat = latitude # pylint: disable=assigning-non-slot
|
|
||||||
except ValueError:
|
|
||||||
errors.append("invalid value for latitude given: {}".format(latitude))
|
|
||||||
|
|
||||||
try:
|
|
||||||
observer.long = longitude # pylint: disable=assigning-non-slot
|
|
||||||
except ValueError:
|
|
||||||
errors.append("invalid value for latitude given: {}".format(latitude))
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
logger.error("Error setting up: %s", ", ".join(errors))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_sun_state(now):
|
try:
|
||||||
""" Method to update the current state of the sun and
|
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
|
||||||
set time of next setting and rising. """
|
except ValueError:
|
||||||
utc_offset = datetime.utcnow() - datetime.now()
|
# Raised when invalid latitude or longitude is given to Observer
|
||||||
utc_now = now + utc_offset
|
logger.exception("Invalid value for latitude or longitude")
|
||||||
|
return False
|
||||||
|
|
||||||
observer = ephem.Observer()
|
sun.point_in_time_listener(dt_util.utcnow())
|
||||||
observer.lat = latitude # pylint: disable=assigning-non-slot
|
|
||||||
observer.long = longitude # pylint: disable=assigning-non-slot
|
|
||||||
|
|
||||||
next_rising_dt = ephem.localtime(
|
|
||||||
observer.next_rising(sun, start=utc_now))
|
|
||||||
next_setting_dt = ephem.localtime(
|
|
||||||
observer.next_setting(sun, start=utc_now))
|
|
||||||
|
|
||||||
if next_rising_dt > next_setting_dt:
|
|
||||||
new_state = STATE_ABOVE_HORIZON
|
|
||||||
next_change = next_setting_dt
|
|
||||||
|
|
||||||
else:
|
|
||||||
new_state = STATE_BELOW_HORIZON
|
|
||||||
next_change = next_rising_dt
|
|
||||||
|
|
||||||
logger.info("%s. Next change: %s",
|
|
||||||
new_state, next_change.strftime("%H:%M"))
|
|
||||||
|
|
||||||
state_attributes = {
|
|
||||||
STATE_ATTR_NEXT_RISING: datetime_to_str(next_rising_dt),
|
|
||||||
STATE_ATTR_NEXT_SETTING: datetime_to_str(next_setting_dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
hass.states.set(ENTITY_ID, new_state, state_attributes)
|
|
||||||
|
|
||||||
# +1 second so Ephem will report it has set
|
|
||||||
hass.track_point_in_time(update_sun_state,
|
|
||||||
next_change + timedelta(seconds=1))
|
|
||||||
|
|
||||||
update_sun_state(datetime.now())
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Sun(Entity):
|
||||||
|
""" Represents the Sun. """
|
||||||
|
|
||||||
|
entity_id = ENTITY_ID
|
||||||
|
|
||||||
|
def __init__(self, hass, latitude, longitude):
|
||||||
|
self.hass = hass
|
||||||
|
self.observer = ephem.Observer()
|
||||||
|
# pylint: disable=assigning-non-slot
|
||||||
|
self.observer.lat = latitude
|
||||||
|
# pylint: disable=assigning-non-slot
|
||||||
|
self.observer.long = longitude
|
||||||
|
|
||||||
|
self._state = self.next_rising = self.next_setting = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" We trigger updates ourselves after sunset/sunrise """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return "Sun"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
if self.next_rising > self.next_setting:
|
||||||
|
return STATE_ABOVE_HORIZON
|
||||||
|
|
||||||
|
return STATE_BELOW_HORIZON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
return {
|
||||||
|
STATE_ATTR_NEXT_RISING: dt_util.datetime_to_str(self.next_rising),
|
||||||
|
STATE_ATTR_NEXT_SETTING: dt_util.datetime_to_str(self.next_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_change(self):
|
||||||
|
""" Returns the datetime when the next change to the state is. """
|
||||||
|
return min(self.next_rising, self.next_setting)
|
||||||
|
|
||||||
|
def update_as_of(self, utc_point_in_time):
|
||||||
|
""" Calculate sun state at a point in UTC time. """
|
||||||
|
sun = ephem.Sun() # pylint: disable=no-member
|
||||||
|
|
||||||
|
# pylint: disable=assigning-non-slot
|
||||||
|
self.observer.date = ephem.date(utc_point_in_time)
|
||||||
|
|
||||||
|
self.next_rising = self.observer.next_rising(
|
||||||
|
sun,
|
||||||
|
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
|
self.next_setting = self.observer.next_setting(
|
||||||
|
sun,
|
||||||
|
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
def point_in_time_listener(self, now):
|
||||||
|
""" Called when the state of the sun has changed. """
|
||||||
|
self.update_as_of(now)
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
# Schedule next update at next_change+1 second so sun state has changed
|
||||||
|
self.hass.track_point_in_utc_time(
|
||||||
|
self.point_in_time_listener,
|
||||||
|
self.next_change + timedelta(seconds=1))
|
||||||
|
|
||||||
|
|
||||||
def create_event_listener(schedule, event_listener_data):
|
def create_event_listener(schedule, event_listener_data):
|
||||||
""" Create a sun event listener based on the description. """
|
""" Create a sun event listener based on the description. """
|
||||||
|
|
||||||
@ -195,7 +233,7 @@ class SunEventListener(ServiceEventListener):
|
|||||||
else:
|
else:
|
||||||
next_time = next_event + self.offset
|
next_time = next_event + self.offset
|
||||||
|
|
||||||
while next_time < datetime.now() or \
|
while next_time < dt_util.now() or \
|
||||||
next_time.weekday() not in self.my_schedule.days:
|
next_time.weekday() not in self.my_schedule.days:
|
||||||
next_time = next_time + timedelta(days=1)
|
next_time = next_time + timedelta(days=1)
|
||||||
|
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
""" Demo platform that has two fake switches. """
|
"""
|
||||||
|
homeassistant.components.switch.demo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Demo platform that has two fake switches.
|
||||||
|
|
||||||
|
"""
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
|
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
""" Support for ISY994 switch. """
|
"""
|
||||||
# system imports
|
homeassistant.components.switch.isy994
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for ISY994 switches.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# homeassistant imports
|
|
||||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||||
HIDDEN_STRING)
|
HIDDEN_STRING)
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED
|
from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED
|
||||||
@ -12,7 +15,7 @@ from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the isy994 platform. """
|
""" Sets up the ISY994 platform. """
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
devs = []
|
devs = []
|
||||||
@ -54,7 +57,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class ISYSwitchDevice(ISYDeviceABC):
|
class ISYSwitchDevice(ISYDeviceABC):
|
||||||
""" represents as isy light within home assistant. """
|
""" Represents as ISY light. """
|
||||||
|
|
||||||
_domain = 'switch'
|
_domain = 'switch'
|
||||||
_dtype = 'binary'
|
_dtype = 'binary'
|
||||||
@ -62,7 +65,7 @@ class ISYSwitchDevice(ISYDeviceABC):
|
|||||||
|
|
||||||
|
|
||||||
class ISYProgramDevice(ISYSwitchDevice):
|
class ISYProgramDevice(ISYSwitchDevice):
|
||||||
""" represents a door that can be manipulated within home assistant. """
|
""" Represents a door that can be manipulated. """
|
||||||
|
|
||||||
_domain = 'switch'
|
_domain = 'switch'
|
||||||
_dtype = 'binary'
|
_dtype = 'binary'
|
||||||
@ -74,9 +77,9 @@ class ISYProgramDevice(ISYSwitchDevice):
|
|||||||
self.action_node = actions
|
self.action_node = actions
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" turns the device on/closes the device """
|
""" Turns the device on/closes the device. """
|
||||||
self.action_node.runThen()
|
self.action_node.runThen()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" turns the device off/opens the device """
|
""" Turns the device off/opens the device. """
|
||||||
self.action_node.runElse()
|
self.action_node.runElse()
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.switch.modbus
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Support for Modbus switches.
|
Support for Modbus switches.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -53,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class ModbusSwitch(ToggleEntity):
|
class ModbusSwitch(ToggleEntity):
|
||||||
""" Represents a Modbus Switch """
|
""" Represents a Modbus switch. """
|
||||||
|
|
||||||
def __init__(self, name, slave, register, bit):
|
def __init__(self, name, slave, register, bit):
|
||||||
self._name = name
|
self._name = name
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
""" Support for Tellstick switches. """
|
"""
|
||||||
|
homeassistant.components.switch.tellstick
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for Tellstick switches.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
@ -9,7 +14,7 @@ import tellcore.constants as tellcore_constants
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return tellstick switches. """
|
""" Find and return Tellstick switches. """
|
||||||
try:
|
try:
|
||||||
import tellcore.telldus as telldus
|
import tellcore.telldus as telldus
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -30,7 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class TellstickSwitchDevice(ToggleEntity):
|
class TellstickSwitchDevice(ToggleEntity):
|
||||||
""" represents a Tellstick switch within home assistant. """
|
""" Represents a Tellstick switch within Home Assistant. """
|
||||||
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
||||||
tellcore_constants.TELLSTICK_TURNOFF)
|
tellcore_constants.TELLSTICK_TURNOFF)
|
||||||
|
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
""" Support for WeMo switchces. """
|
"""
|
||||||
|
homeassistant.components.switch.wemo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for WeMo switches.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
@ -8,7 +13,7 @@ from homeassistant.components.switch import (
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return wemo switches. """
|
""" Find and return WeMo switches. """
|
||||||
try:
|
try:
|
||||||
# pylint: disable=no-name-in-module, import-error
|
# pylint: disable=no-name-in-module, import-error
|
||||||
import homeassistant.external.pywemo.pywemo as pywemo
|
import homeassistant.external.pywemo.pywemo as pywemo
|
||||||
@ -39,7 +44,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class WemoSwitch(ToggleEntity):
|
class WemoSwitch(ToggleEntity):
|
||||||
""" represents a WeMo switch within home assistant. """
|
""" Represents a WeMo switch within Home Assistant. """
|
||||||
def __init__(self, wemo):
|
def __init__(self, wemo):
|
||||||
self.wemo = wemo
|
self.wemo = wemo
|
||||||
|
|
||||||
@ -78,5 +83,5 @@ class WemoSwitch(ToggleEntity):
|
|||||||
self.wemo.off()
|
self.wemo.off()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Update Wemo state. """
|
""" Update WeMo state. """
|
||||||
self.wemo.get_state(True)
|
self.wemo.get_state(True)
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
""" Support for WeMo switchces. """
|
"""
|
||||||
|
homeassistant.components.switch.wink
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Support for Wink switches.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
Demo platform that offers a fake thermostat.
|
homeassistant.components.thermostat.demo
|
||||||
"""
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Demo platform that offers a fake thermostat.
|
||||||
|
|
||||||
|
"""
|
||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.wink
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Connects to a Wink hub and loads relevant components to control its devices.
|
Connects to a Wink hub and loads relevant components to control its devices.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
@ -53,14 +56,14 @@ def setup(hass, config):
|
|||||||
|
|
||||||
|
|
||||||
class WinkToggleDevice(ToggleEntity):
|
class WinkToggleDevice(ToggleEntity):
|
||||||
""" represents a Wink switch within home assistant. """
|
""" Represents a Wink switch within Home Assistant. """
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
self.wink = wink
|
self.wink = wink
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
""" Returns the id of this WeMo switch """
|
""" Returns the id of this Wink switch. """
|
||||||
return "{}.{}".format(self.__class__, self.wink.deviceId())
|
return "{}.{}".format(self.__class__, self.wink.deviceId())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
140
homeassistant/config.py
Normal file
140
homeassistant/config.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.config
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Module to help with parsing and generating configuration files.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from homeassistant import HomeAssistantError
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||||
|
CONF_TIME_ZONE)
|
||||||
|
import homeassistant.util as util
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||||
|
CONF_CONFIG_FILE = 'home-assistant.conf'
|
||||||
|
DEFAULT_COMPONENTS = [
|
||||||
|
'discovery', 'frontend', 'conversation', 'history', 'logbook']
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_config_exists(config_dir, detect_location=True):
|
||||||
|
""" Ensures a config file exists in given config dir.
|
||||||
|
Creating a default one if needed.
|
||||||
|
Returns path to the config file. """
|
||||||
|
config_path = find_config_file(config_dir)
|
||||||
|
|
||||||
|
if config_path is None:
|
||||||
|
_LOGGER.info("Unable to find configuration. Creating default one")
|
||||||
|
config_path = create_default_config(config_dir, detect_location)
|
||||||
|
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_config(config_dir, detect_location=True):
|
||||||
|
""" Creates a default configuration file in given config dir.
|
||||||
|
Returns path to new config file if success, None if failed. """
|
||||||
|
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
||||||
|
|
||||||
|
# Writing files with YAML does not create the most human readable results
|
||||||
|
# So we're hard coding a YAML template.
|
||||||
|
try:
|
||||||
|
with open(config_path, 'w') as config_file:
|
||||||
|
location_info = detect_location and util.detect_location_info()
|
||||||
|
|
||||||
|
if location_info:
|
||||||
|
temp_unit = 'F' if location_info.use_fahrenheit else 'C'
|
||||||
|
|
||||||
|
auto_config = {
|
||||||
|
CONF_NAME: 'Home',
|
||||||
|
CONF_LATITUDE: location_info.latitude,
|
||||||
|
CONF_LONGITUDE: location_info.longitude,
|
||||||
|
CONF_TEMPERATURE_UNIT: temp_unit,
|
||||||
|
CONF_TIME_ZONE: location_info.time_zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_file.write("homeassistant:\n")
|
||||||
|
|
||||||
|
for key, value in auto_config.items():
|
||||||
|
config_file.write(" {}: {}\n".format(key, value))
|
||||||
|
|
||||||
|
config_file.write("\n")
|
||||||
|
|
||||||
|
for component in DEFAULT_COMPONENTS:
|
||||||
|
config_file.write("{}:\n\n".format(component))
|
||||||
|
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
'Unable to write default configuration file %s', config_path)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_config_file(config_dir):
|
||||||
|
""" Looks in given directory for supported config files. """
|
||||||
|
for filename in (YAML_CONFIG_FILE, CONF_CONFIG_FILE):
|
||||||
|
config_path = os.path.join(config_dir, filename)
|
||||||
|
|
||||||
|
if os.path.isfile(config_path):
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_config_file(config_path):
|
||||||
|
""" Loads given config file. """
|
||||||
|
config_ext = os.path.splitext(config_path)[1]
|
||||||
|
|
||||||
|
if config_ext == '.yaml':
|
||||||
|
return load_yaml_config_file(config_path)
|
||||||
|
|
||||||
|
elif config_ext == '.conf':
|
||||||
|
return load_conf_config_file(config_path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml_config_file(config_path):
|
||||||
|
""" Parse a YAML configuration file. """
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config_path) as conf_file:
|
||||||
|
# If configuration file is empty YAML returns None
|
||||||
|
# We convert that to an empty dict
|
||||||
|
conf_dict = yaml.load(conf_file) or {}
|
||||||
|
|
||||||
|
except yaml.YAMLError:
|
||||||
|
_LOGGER.exception('Error reading YAML configuration file')
|
||||||
|
raise HomeAssistantError()
|
||||||
|
|
||||||
|
if not isinstance(conf_dict, dict):
|
||||||
|
_LOGGER.error(
|
||||||
|
'The configuration file %s does not contain a dictionary',
|
||||||
|
os.path.basename(config_path))
|
||||||
|
raise HomeAssistantError()
|
||||||
|
|
||||||
|
return conf_dict
|
||||||
|
|
||||||
|
|
||||||
|
def load_conf_config_file(config_path):
|
||||||
|
""" Parse the old style conf configuration. """
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
config_dict = {}
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(config_path)
|
||||||
|
|
||||||
|
for section in config.sections():
|
||||||
|
config_dict[section] = {}
|
||||||
|
|
||||||
|
for key, val in config.items(section):
|
||||||
|
config_dict[section][key] = val
|
||||||
|
|
||||||
|
return config_dict
|
@ -11,7 +11,7 @@ CONF_LONGITUDE = "longitude"
|
|||||||
CONF_TEMPERATURE_UNIT = "temperature_unit"
|
CONF_TEMPERATURE_UNIT = "temperature_unit"
|
||||||
CONF_NAME = "name"
|
CONF_NAME = "name"
|
||||||
CONF_TIME_ZONE = "time_zone"
|
CONF_TIME_ZONE = "time_zone"
|
||||||
CONF_VISIBILITY = "visibility"
|
CONF_CUSTOMIZE = "customize"
|
||||||
|
|
||||||
CONF_PLATFORM = "platform"
|
CONF_PLATFORM = "platform"
|
||||||
CONF_HOST = "host"
|
CONF_HOST = "host"
|
||||||
@ -111,6 +111,7 @@ SERVER_PORT = 8123
|
|||||||
URL_ROOT = "/"
|
URL_ROOT = "/"
|
||||||
URL_API = "/api/"
|
URL_API = "/api/"
|
||||||
URL_API_STREAM = "/api/stream"
|
URL_API_STREAM = "/api/stream"
|
||||||
|
URL_API_CONFIG = "/api/config"
|
||||||
URL_API_STATES = "/api/states"
|
URL_API_STATES = "/api/states"
|
||||||
URL_API_STATES_ENTITY = "/api/states/{}"
|
URL_API_STATES_ENTITY = "/api/states/{}"
|
||||||
URL_API_EVENTS = "/api/events"
|
URL_API_EVENTS = "/api/events"
|
||||||
@ -119,6 +120,7 @@ URL_API_SERVICES = "/api/services"
|
|||||||
URL_API_SERVICES_SERVICE = "/api/services/{}/{}"
|
URL_API_SERVICES_SERVICE = "/api/services/{}/{}"
|
||||||
URL_API_EVENT_FORWARD = "/api/event_forwarding"
|
URL_API_EVENT_FORWARD = "/api/event_forwarding"
|
||||||
URL_API_COMPONENTS = "/api/components"
|
URL_API_COMPONENTS = "/api/components"
|
||||||
|
URL_API_BOOTSTRAP = "/api/bootstrap"
|
||||||
|
|
||||||
HTTP_OK = 200
|
HTTP_OK = 200
|
||||||
HTTP_CREATED = 201
|
HTTP_CREATED = 201
|
||||||
|
2
homeassistant/external/netdisco
vendored
2
homeassistant/external/netdisco
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 6e712dd65e474bf623b35c54f5290dbac192c7e4
|
Subproject commit b2cad7c2b959efa8eee9b5ac62d87232bf0b5176
|
2
homeassistant/external/noop
vendored
2
homeassistant/external/noop
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 45fae73c1f44342010fa07f3ed8909bf2819a508
|
Subproject commit 4eaeb3367f9ada05dae3319cf24ab1da5de1aa89
|
2
homeassistant/external/nzbclients
vendored
2
homeassistant/external/nzbclients
vendored
@ -1 +1 @@
|
|||||||
Subproject commit f9f9ba36934f087b9c4241303b900794a7eb6c08
|
Subproject commit f01997498fe190d6ac2a2c375a739024843bd44d
|
1
homeassistant/external/pymysensors
vendored
Submodule
1
homeassistant/external/pymysensors
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit cd5ef892eeec0ad027727f7e8f757e7f2927da97
|
2
homeassistant/external/pywemo
vendored
2
homeassistant/external/pywemo
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 7f6c383ded75f1273cbca28e858b8a8c96da66d4
|
Subproject commit ca94e41faa48c783f600a2efd550c6b7dae01b0d
|
@ -5,14 +5,17 @@ homeassistant.helpers.entity
|
|||||||
Provides ABC for entities in HA.
|
Provides ABC for entities in HA.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from homeassistant import NoEntitySpecifiedError
|
from homeassistant import NoEntitySpecifiedError
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON,
|
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN,
|
||||||
STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS,
|
||||||
|
TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
# Dict mapping entity_id to a boolean that overwrites the hidden property
|
# Dict mapping entity_id to a boolean that overwrites the hidden property
|
||||||
_OVERWRITE_HIDDEN = {}
|
_OVERWRITE = defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
class Entity(object):
|
class Entity(object):
|
||||||
@ -121,8 +124,15 @@ class Entity(object):
|
|||||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
||||||
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
||||||
|
|
||||||
if _OVERWRITE_HIDDEN.get(self.entity_id, self.hidden):
|
if self.hidden:
|
||||||
attr[ATTR_HIDDEN] = True
|
attr[ATTR_HIDDEN] = self.hidden
|
||||||
|
|
||||||
|
# overwrite properties that have been set in the config file
|
||||||
|
attr.update(_OVERWRITE.get(self.entity_id, {}))
|
||||||
|
|
||||||
|
# remove hidden property if false so it won't show up
|
||||||
|
if not attr.get(ATTR_HIDDEN, True):
|
||||||
|
attr.pop(ATTR_HIDDEN)
|
||||||
|
|
||||||
# Convert temperature if we detect one
|
# Convert temperature if we detect one
|
||||||
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
||||||
@ -143,15 +153,18 @@ class Entity(object):
|
|||||||
return "<Entity {}: {}>".format(self.name, self.state)
|
return "<Entity {}: {}>".format(self.name, self.state)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def overwrite_hidden(entity_id, hidden):
|
def overwrite_attribute(entity_id, attrs, vals):
|
||||||
"""
|
"""
|
||||||
Overwrite the hidden property of an entity.
|
Overwrite any attribute of an entity.
|
||||||
Set hidden to None to remove any overwritten value in place.
|
This function should receive a list of attributes and a
|
||||||
|
list of values. Set attribute to None to remove any overwritten
|
||||||
|
value in place.
|
||||||
"""
|
"""
|
||||||
if hidden is None:
|
for attr, val in zip(attrs, vals):
|
||||||
_OVERWRITE_HIDDEN.pop(entity_id, None)
|
if val is None:
|
||||||
else:
|
_OVERWRITE[entity_id.lower()].pop(attr, None)
|
||||||
_OVERWRITE_HIDDEN[entity_id.lower()] = hidden
|
else:
|
||||||
|
_OVERWRITE[entity_id.lower()][attr] = val
|
||||||
|
|
||||||
|
|
||||||
class ToggleEntity(Entity):
|
class ToggleEntity(Entity):
|
||||||
|
@ -4,7 +4,7 @@ homeassistant.helpers.entity_component
|
|||||||
|
|
||||||
Provides helpers for components that manage entities.
|
Provides helpers for components that manage entities.
|
||||||
"""
|
"""
|
||||||
from homeassistant.loader import get_component
|
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.components import group, discovery
|
from homeassistant.components import group, discovery
|
||||||
@ -35,12 +35,16 @@ class EntityComponent(object):
|
|||||||
self.group = None
|
self.group = None
|
||||||
self.is_polling = False
|
self.is_polling = False
|
||||||
|
|
||||||
|
self.config = None
|
||||||
|
|
||||||
def setup(self, config):
|
def setup(self, config):
|
||||||
"""
|
"""
|
||||||
Sets up a full entity component:
|
Sets up a full entity component:
|
||||||
- Loads the platforms from the config
|
- Loads the platforms from the config
|
||||||
- Will listen for supported discovered platforms
|
- Will listen for supported discovered platforms
|
||||||
"""
|
"""
|
||||||
|
self.config = config
|
||||||
|
|
||||||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||||
for p_type, p_config in \
|
for p_type, p_config in \
|
||||||
config_per_platform(config, self.domain, self.logger):
|
config_per_platform(config, self.domain, self.logger):
|
||||||
@ -115,18 +119,20 @@ class EntityComponent(object):
|
|||||||
self._update_entity_states,
|
self._update_entity_states,
|
||||||
second=range(0, 60, self.scan_interval))
|
second=range(0, 60, self.scan_interval))
|
||||||
|
|
||||||
def _setup_platform(self, platform_type, config, discovery_info=None):
|
def _setup_platform(self, platform_type, platform_config,
|
||||||
|
discovery_info=None):
|
||||||
""" Tries to setup a platform for this component. """
|
""" Tries to setup a platform for this component. """
|
||||||
platform_name = '{}.{}'.format(self.domain, platform_type)
|
platform = prepare_setup_platform(
|
||||||
platform = get_component(platform_name)
|
self.hass, self.config, self.domain, platform_type)
|
||||||
|
|
||||||
if platform is None:
|
if platform is None:
|
||||||
self.logger.error('Unable to find platform %s', platform_type)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
platform_name = '{}.{}'.format(self.domain, platform_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
platform.setup_platform(
|
platform.setup_platform(
|
||||||
self.hass, 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)
|
||||||
|
|
||||||
@ -135,15 +141,16 @@ class EntityComponent(object):
|
|||||||
# Support old deprecated method for now - 3/1/2015
|
# Support old deprecated method for now - 3/1/2015
|
||||||
if hasattr(platform, 'get_devices'):
|
if hasattr(platform, 'get_devices'):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Please upgrade %s to return new entities using "
|
'Please upgrade %s to return new entities using '
|
||||||
"setup_platform. See %s/demo.py for an example.",
|
'setup_platform. See %s/demo.py for an example.',
|
||||||
platform_name, self.domain)
|
platform_name, self.domain)
|
||||||
self.add_entities(platform.get_devices(self.hass, config))
|
self.add_entities(
|
||||||
|
platform.get_devices(self.hass, platform_config))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.exception(
|
self.logger.exception(
|
||||||
"Error while setting up platform %s", platform_type)
|
'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)
|
||||||
|
@ -5,9 +5,9 @@ homeassistant.helpers.state
|
|||||||
Helpers that help with state related things.
|
Helpers that help with state related things.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from homeassistant import State
|
from homeassistant import State
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class TrackStates(object):
|
|||||||
self.states = []
|
self.states = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.now = datetime.now()
|
self.now = dt_util.utcnow()
|
||||||
return self.states
|
return self.states
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
@ -8,7 +8,7 @@ import collections
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import enum
|
import enum
|
||||||
import socket
|
import socket
|
||||||
@ -16,12 +16,19 @@ import random
|
|||||||
import string
|
import string
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package
|
||||||
|
# pylint: disable=unused-import
|
||||||
|
from .dt import ( # noqa
|
||||||
|
datetime_to_str, str_to_datetime, strip_microseconds,
|
||||||
|
datetime_to_local_str, utcnow)
|
||||||
|
|
||||||
|
|
||||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||||
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
||||||
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
||||||
|
|
||||||
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
""" Sanitizes a filename by removing .. / and \\. """
|
""" Sanitizes a filename by removing .. / and \\. """
|
||||||
@ -40,33 +47,6 @@ def slugify(text):
|
|||||||
return RE_SLUGIFY.sub("", text)
|
return RE_SLUGIFY.sub("", text)
|
||||||
|
|
||||||
|
|
||||||
def datetime_to_str(dattim):
|
|
||||||
""" Converts datetime to a string format.
|
|
||||||
|
|
||||||
@rtype : str
|
|
||||||
"""
|
|
||||||
return dattim.strftime(DATE_STR_FORMAT)
|
|
||||||
|
|
||||||
|
|
||||||
def str_to_datetime(dt_str):
|
|
||||||
""" Converts a string to a datetime object.
|
|
||||||
|
|
||||||
@rtype: datetime
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return datetime.strptime(dt_str, DATE_STR_FORMAT)
|
|
||||||
except ValueError: # If dt_str did not match our format
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def strip_microseconds(dattim):
|
|
||||||
""" Returns a copy of dattime object but with microsecond set to 0. """
|
|
||||||
if dattim.microsecond:
|
|
||||||
return dattim - timedelta(microseconds=dattim.microsecond)
|
|
||||||
else:
|
|
||||||
return dattim
|
|
||||||
|
|
||||||
|
|
||||||
def split_entity_id(entity_id):
|
def split_entity_id(entity_id):
|
||||||
""" Splits a state entity_id into domain, object_id. """
|
""" Splits a state entity_id into domain, object_id. """
|
||||||
return entity_id.split(".", 1)
|
return entity_id.split(".", 1)
|
||||||
@ -79,7 +59,7 @@ def repr_helper(inp):
|
|||||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||||
in inp.items())
|
in inp.items())
|
||||||
elif isinstance(inp, datetime):
|
elif isinstance(inp, datetime):
|
||||||
return datetime_to_str(inp)
|
return datetime_to_local_str(inp)
|
||||||
else:
|
else:
|
||||||
return str(inp)
|
return str(inp)
|
||||||
|
|
||||||
@ -174,6 +154,32 @@ def get_random_string(length=10):
|
|||||||
return ''.join(generator.choice(source_chars) for _ in range(length))
|
return ''.join(generator.choice(source_chars) for _ in range(length))
|
||||||
|
|
||||||
|
|
||||||
|
LocationInfo = collections.namedtuple(
|
||||||
|
"LocationInfo",
|
||||||
|
['ip', 'country_code', 'country_name', 'region_code', 'region_name',
|
||||||
|
'city', 'zip_code', 'time_zone', 'latitude', 'longitude',
|
||||||
|
'use_fahrenheit'])
|
||||||
|
|
||||||
|
|
||||||
|
def detect_location_info():
|
||||||
|
""" Detect location information. """
|
||||||
|
try:
|
||||||
|
raw_info = requests.get(
|
||||||
|
'https://freegeoip.net/json/', timeout=5).json()
|
||||||
|
except requests.RequestException:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = {key: raw_info.get(key) for key in LocationInfo._fields}
|
||||||
|
|
||||||
|
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
|
||||||
|
# the Cayman Islands, Palau, and the United States and associated
|
||||||
|
# territories of American Samoa and the U.S. Virgin Islands
|
||||||
|
data['use_fahrenheit'] = data['country_code'] in (
|
||||||
|
'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI')
|
||||||
|
|
||||||
|
return LocationInfo(**data)
|
||||||
|
|
||||||
|
|
||||||
class OrderedEnum(enum.Enum):
|
class OrderedEnum(enum.Enum):
|
||||||
""" Taken from Python 3.4.0 docs. """
|
""" Taken from Python 3.4.0 docs. """
|
||||||
# pylint: disable=no-init, too-few-public-methods
|
# pylint: disable=no-init, too-few-public-methods
|
||||||
@ -436,7 +442,7 @@ class ThreadPool(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Add to current running jobs
|
# Add to current running jobs
|
||||||
job_log = (datetime.now(), job)
|
job_log = (utcnow(), job)
|
||||||
self.current_jobs.append(job_log)
|
self.current_jobs.append(job_log)
|
||||||
|
|
||||||
# Do the job
|
# Do the job
|
114
homeassistant/util/dt.py
Normal file
114
homeassistant/util/dt.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.util.dt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Provides helper methods to handle the time in HA.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
|
||||||
|
DATE_SHORT_STR_FORMAT = "%Y-%m-%d"
|
||||||
|
TIME_SHORT_STR_FORMAT = "%H:%M"
|
||||||
|
UTC = DEFAULT_TIME_ZONE = pytz.utc
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_time_zone(time_zone):
|
||||||
|
""" Sets a default time zone to be used when none is specified. """
|
||||||
|
global DEFAULT_TIME_ZONE # pylint: disable=global-statement
|
||||||
|
|
||||||
|
assert isinstance(time_zone, dt.tzinfo)
|
||||||
|
|
||||||
|
DEFAULT_TIME_ZONE = time_zone
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_zone(time_zone_str):
|
||||||
|
""" Get time zone from string. Return None if unable to determine. """
|
||||||
|
try:
|
||||||
|
return pytz.timezone(time_zone_str)
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def utcnow():
|
||||||
|
""" Get now in UTC time. """
|
||||||
|
return dt.datetime.now(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def now(time_zone=None):
|
||||||
|
""" Get now in specified time zone. """
|
||||||
|
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
def as_utc(dattim):
|
||||||
|
""" Return a datetime as UTC time.
|
||||||
|
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """
|
||||||
|
if dattim.tzinfo == pytz.utc:
|
||||||
|
return dattim
|
||||||
|
elif dattim.tzinfo is None:
|
||||||
|
dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
return dattim.astimezone(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def as_local(dattim):
|
||||||
|
""" Converts a UTC datetime object to local time_zone. """
|
||||||
|
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
||||||
|
return dattim
|
||||||
|
elif dattim.tzinfo is None:
|
||||||
|
dattim = dattim.replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
return dattim.astimezone(DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
def utc_from_timestamp(timestamp):
|
||||||
|
""" Returns a UTC time from a timestamp. """
|
||||||
|
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_local_str(dattim, time_zone=None):
|
||||||
|
""" Converts datetime to specified time_zone and returns a string. """
|
||||||
|
return datetime_to_str(as_local(dattim))
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_str(dattim):
|
||||||
|
""" Converts datetime to a string format.
|
||||||
|
|
||||||
|
@rtype : str
|
||||||
|
"""
|
||||||
|
return dattim.strftime(DATE_STR_FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_short_time_str(dattim):
|
||||||
|
""" Converts datetime to a string format as short time.
|
||||||
|
|
||||||
|
@rtype : str
|
||||||
|
"""
|
||||||
|
return dattim.strftime(TIME_SHORT_STR_FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_short_date_str(dattim):
|
||||||
|
""" Converts datetime to a string format as short date.
|
||||||
|
|
||||||
|
@rtype : str
|
||||||
|
"""
|
||||||
|
return dattim.strftime(DATE_SHORT_STR_FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_datetime(dt_str):
|
||||||
|
""" Converts a string to a UTC datetime object.
|
||||||
|
|
||||||
|
@rtype: datetime
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return dt.datetime.strptime(
|
||||||
|
dt_str, DATE_STR_FORMAT).replace(tzinfo=pytz.utc)
|
||||||
|
except ValueError: # If dt_str did not match our format
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def strip_microseconds(dattim):
|
||||||
|
""" Returns a copy of dattime object but with microsecond set to 0. """
|
||||||
|
return dattim.replace(microsecond=0)
|
@ -1,6 +1,7 @@
|
|||||||
# required for Home Assistant core
|
# required for Home Assistant core
|
||||||
requests>=2.0
|
requests>=2.0
|
||||||
pyyaml>=3.11
|
pyyaml>=3.11
|
||||||
|
pytz>=2015.2
|
||||||
|
|
||||||
# optional, needed for specific components
|
# optional, needed for specific components
|
||||||
|
|
||||||
@ -40,8 +41,15 @@ PyISY>=1.0.2
|
|||||||
# sensor.systemmonitor
|
# sensor.systemmonitor
|
||||||
psutil>=2.2.1
|
psutil>=2.2.1
|
||||||
|
|
||||||
#pushover notifications
|
# pushover notifications
|
||||||
python-pushover>=0.2
|
python-pushover>=0.2
|
||||||
|
|
||||||
# Transmission Torrent Client
|
# Transmission Torrent Client
|
||||||
transmissionrpc>=0.11
|
transmissionrpc>=0.11
|
||||||
|
|
||||||
|
# OpenWeatherMap Web API
|
||||||
|
pyowm>=2.2.0
|
||||||
|
|
||||||
|
# XMPP Bindings (notify.xmpp)
|
||||||
|
sleekxmpp>=1.3.1
|
||||||
|
|
||||||
|
@ -3,4 +3,8 @@ if [ ${PWD##*/} == "scripts" ]; then
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m unittest discover tests
|
if [ "$1" = "coverage" ]; then
|
||||||
|
coverage run -m unittest discover tests
|
||||||
|
else
|
||||||
|
python3 -m unittest discover tests
|
||||||
|
fi
|
||||||
|
@ -26,6 +26,10 @@ class MockScanner(object):
|
|||||||
""" Make a device leave the house. """
|
""" Make a device leave the house. """
|
||||||
self.devices_home.remove(device)
|
self.devices_home.remove(device)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
""" Resets which devices are home. """
|
||||||
|
self.devices_home = []
|
||||||
|
|
||||||
def scan_devices(self):
|
def scan_devices(self):
|
||||||
""" Returns a list of fake devices. """
|
""" Returns a list of fake devices. """
|
||||||
|
|
||||||
|
@ -5,16 +5,36 @@ tests.helper
|
|||||||
Helper method for writing tests.
|
Helper method for writing tests.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
|
from homeassistant.const import (
|
||||||
|
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
|
||||||
|
EVENT_STATE_CHANGED)
|
||||||
|
from homeassistant.components import sun
|
||||||
|
|
||||||
|
|
||||||
def get_test_home_assistant():
|
def get_test_config_dir():
|
||||||
|
""" Returns a path to a test config dir. """
|
||||||
|
return os.path.join(os.path.dirname(__file__), "config")
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_home_assistant(num_threads=None):
|
||||||
""" Returns a Home Assistant object pointing at test config dir. """
|
""" Returns a Home Assistant object pointing at test config dir. """
|
||||||
|
if num_threads:
|
||||||
|
orig_num_threads = ha.MIN_WORKER_THREAD
|
||||||
|
ha.MIN_WORKER_THREAD = num_threads
|
||||||
|
|
||||||
hass = ha.HomeAssistant()
|
hass = ha.HomeAssistant()
|
||||||
hass.config.config_dir = os.path.join(os.path.dirname(__file__), "config")
|
|
||||||
|
if num_threads:
|
||||||
|
ha.MIN_WORKER_THREAD = orig_num_threads
|
||||||
|
|
||||||
|
hass.config.config_dir = get_test_config_dir()
|
||||||
|
hass.config.latitude = 32.87336
|
||||||
|
hass.config.longitude = -117.22743
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
@ -32,6 +52,44 @@ def mock_service(hass, domain, service):
|
|||||||
return calls
|
return calls
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_device_tracker_scan(hass):
|
||||||
|
""" Triggers the device tracker to scan. """
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
dt_util.utcnow().replace(second=0) + timedelta(hours=1)})
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_sun_risen(hass):
|
||||||
|
""" Trigger sun to rise if below horizon. """
|
||||||
|
if not sun.is_on(hass):
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
sun.next_rising_utc(hass) + timedelta(seconds=10)})
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_sun_set(hass):
|
||||||
|
""" Trigger sun to set if above horizon. """
|
||||||
|
if sun.is_on(hass):
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
sun.next_setting_utc(hass) + timedelta(seconds=10)})
|
||||||
|
|
||||||
|
|
||||||
|
def mock_state_change_event(hass, new_state, old_state=None):
|
||||||
|
event_data = {
|
||||||
|
'entity_id': new_state.entity_id,
|
||||||
|
'new_state': new_state,
|
||||||
|
}
|
||||||
|
|
||||||
|
if old_state:
|
||||||
|
event_data['old_state'] = old_state
|
||||||
|
|
||||||
|
hass.bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||||
|
|
||||||
|
|
||||||
class MockModule(object):
|
class MockModule(object):
|
||||||
""" Provides a fake module. """
|
""" Provides a fake module. """
|
||||||
|
|
||||||
|
@ -4,13 +4,10 @@ tests.test_component_demo
|
|||||||
|
|
||||||
Tests demo component.
|
Tests demo component.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.components.demo as demo
|
import homeassistant.components.demo as demo
|
||||||
from homeassistant.const import (
|
|
||||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON, STATE_OFF, ATTR_ENTITY_ID)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDemo(unittest.TestCase):
|
class TestDemo(unittest.TestCase):
|
||||||
@ -23,46 +20,6 @@ class TestDemo(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_services(self):
|
|
||||||
""" Test the demo services. """
|
|
||||||
# Test turning on and off different types
|
|
||||||
demo.setup(self.hass, {})
|
|
||||||
|
|
||||||
for domain in ('light', 'switch'):
|
|
||||||
# Focus on 1 entity
|
|
||||||
entity_id = self.hass.states.entity_ids(domain)[0]
|
|
||||||
|
|
||||||
self.hass.services.call(
|
|
||||||
domain, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
|
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(STATE_ON, self.hass.states.get(entity_id).state)
|
|
||||||
|
|
||||||
self.hass.services.call(
|
|
||||||
domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
|
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(STATE_OFF, self.hass.states.get(entity_id).state)
|
|
||||||
|
|
||||||
# Act on all
|
|
||||||
self.hass.services.call(domain, SERVICE_TURN_ON)
|
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
for entity_id in self.hass.states.entity_ids(domain):
|
|
||||||
self.assertEqual(
|
|
||||||
STATE_ON, self.hass.states.get(entity_id).state)
|
|
||||||
|
|
||||||
self.hass.services.call(domain, SERVICE_TURN_OFF)
|
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
for entity_id in self.hass.states.entity_ids(domain):
|
|
||||||
self.assertEqual(
|
|
||||||
STATE_OFF, self.hass.states.get(entity_id).state)
|
|
||||||
|
|
||||||
def test_if_demo_state_shows_by_default(self):
|
def test_if_demo_state_shows_by_default(self):
|
||||||
""" Test if demo state shows if we give no configuration. """
|
""" Test if demo state shows if we give no configuration. """
|
||||||
demo.setup(self.hass, {demo.DOMAIN: {}})
|
demo.setup(self.hass, {demo.DOMAIN: {}})
|
||||||
|
127
tests/test_component_device_sun_light_trigger.py
Normal file
127
tests/test_component_device_sun_light_trigger.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_device_sun_light_trigger
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests device sun light trigger component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import homeassistant.loader as loader
|
||||||
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
from homeassistant.components import (
|
||||||
|
device_tracker, light, sun, device_sun_light_trigger)
|
||||||
|
|
||||||
|
|
||||||
|
from helpers import (
|
||||||
|
get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
|
||||||
|
trigger_device_tracker_scan)
|
||||||
|
|
||||||
|
|
||||||
|
KNOWN_DEV_PATH = None
|
||||||
|
|
||||||
|
|
||||||
|
def setUpModule(): # pylint: disable=invalid-name
|
||||||
|
""" Initalizes a Home Assistant server. """
|
||||||
|
global KNOWN_DEV_PATH
|
||||||
|
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
loader.prepare(hass)
|
||||||
|
KNOWN_DEV_PATH = hass.config.path(
|
||||||
|
device_tracker.KNOWN_DEVICES_FILE)
|
||||||
|
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
with open(KNOWN_DEV_PATH, 'w') as fil:
|
||||||
|
fil.write('device,name,track,picture\n')
|
||||||
|
fil.write('DEV1,device 1,1,http://example.com/dev1.jpg\n')
|
||||||
|
fil.write('DEV2,device 2,1,http://example.com/dev2.jpg\n')
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
""" Stops the Home Assistant server. """
|
||||||
|
os.remove(KNOWN_DEV_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeviceSunLightTrigger(unittest.TestCase):
|
||||||
|
""" Test the device sun light trigger module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
self.scanner = loader.get_component(
|
||||||
|
'device_tracker.test').get_scanner(None, None)
|
||||||
|
|
||||||
|
self.scanner.reset()
|
||||||
|
self.scanner.come_home('DEV1')
|
||||||
|
|
||||||
|
loader.get_component('light.test').init()
|
||||||
|
|
||||||
|
device_tracker.setup(self.hass, {
|
||||||
|
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}
|
||||||
|
})
|
||||||
|
|
||||||
|
light.setup(self.hass, {
|
||||||
|
light.DOMAIN: {CONF_PLATFORM: 'test'}
|
||||||
|
})
|
||||||
|
|
||||||
|
sun.setup(self.hass, {})
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_lights_on_when_sun_sets(self):
|
||||||
|
""" Test lights go on when there is someone home and the sun sets. """
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
ensure_sun_risen(self.hass)
|
||||||
|
|
||||||
|
light.turn_off(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
ensure_sun_set(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertTrue(light.is_on(self.hass))
|
||||||
|
|
||||||
|
def test_lights_turn_off_when_everyone_leaves(self):
|
||||||
|
""" Test lights turn off when everyone leaves the house. """
|
||||||
|
light.turn_on(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
self.scanner.leave_home('DEV1')
|
||||||
|
|
||||||
|
trigger_device_tracker_scan(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertFalse(light.is_on(self.hass))
|
||||||
|
|
||||||
|
def test_lights_turn_on_when_coming_home_after_sun_set(self):
|
||||||
|
""" Test lights turn on when coming home after sun set. """
|
||||||
|
light.turn_off(self.hass)
|
||||||
|
|
||||||
|
ensure_sun_set(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
self.scanner.come_home('DEV2')
|
||||||
|
trigger_device_tracker_scan(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertTrue(light.is_on(self.hass))
|
@ -1,17 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_group
|
tests.test_component_device_tracker
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests the group compoments.
|
Tests the device tracker compoments.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
|
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
|
||||||
import homeassistant.components.device_tracker as device_tracker
|
import homeassistant.components.device_tracker as device_tracker
|
||||||
@ -80,6 +81,8 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
scanner = loader.get_component(
|
scanner = loader.get_component(
|
||||||
'device_tracker.test').get_scanner(None, None)
|
'device_tracker.test').get_scanner(None, None)
|
||||||
|
|
||||||
|
scanner.reset()
|
||||||
|
|
||||||
scanner.come_home('DEV1')
|
scanner.come_home('DEV1')
|
||||||
scanner.come_home('DEV2')
|
scanner.come_home('DEV2')
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2')
|
dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2')
|
||||||
dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3')
|
dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3')
|
||||||
|
|
||||||
now = datetime.now()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
# Device scanner scans every 12 seconds. We need to sync our times to
|
# Device scanner scans every 12 seconds. We need to sync our times to
|
||||||
# be every 12 seconds or else the time_changed event will be ignored.
|
# be every 12 seconds or else the time_changed event will be ignored.
|
137
tests/test_component_history.py
Normal file
137
tests/test_component_history.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_history
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the history component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.components import history, recorder, http
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant, mock_state_change_event
|
||||||
|
|
||||||
|
SERVER_PORT = 8126
|
||||||
|
|
||||||
|
|
||||||
|
class TestComponentHistory(unittest.TestCase):
|
||||||
|
""" Tests homeassistant.components.history module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
""" Init needed objects. """
|
||||||
|
self.hass = get_test_home_assistant(1)
|
||||||
|
self.init_rec = False
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
if self.init_rec:
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
os.remove(self.hass.config.path(recorder.DB_FILE))
|
||||||
|
|
||||||
|
def init_recorder(self):
|
||||||
|
recorder.setup(self.hass, {})
|
||||||
|
self.hass.start()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
self.init_rec = True
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
""" Test setup method of history. """
|
||||||
|
http.setup(self.hass, {
|
||||||
|
http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||||
|
self.assertTrue(history.setup(self.hass, {}))
|
||||||
|
|
||||||
|
def test_last_5_states(self):
|
||||||
|
""" Test retrieving the last 5 states. """
|
||||||
|
self.init_recorder()
|
||||||
|
states = []
|
||||||
|
|
||||||
|
entity_id = 'test.last_5_states'
|
||||||
|
|
||||||
|
for i in range(7):
|
||||||
|
self.hass.states.set(entity_id, "State {}".format(i))
|
||||||
|
|
||||||
|
if i > 1:
|
||||||
|
states.append(self.hass.states.get(entity_id))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(reversed(states)), history.last_5_states(entity_id))
|
||||||
|
|
||||||
|
def test_get_states(self):
|
||||||
|
""" Test getting states at a specific point in time. """
|
||||||
|
self.init_recorder()
|
||||||
|
states = []
|
||||||
|
|
||||||
|
# Create 10 states for 5 different entities
|
||||||
|
# After the first 5, sleep a second and save the time
|
||||||
|
# history.get_states takes the latest states BEFORE point X
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
state = ha.State(
|
||||||
|
'test.point_in_time_{}'.format(i % 5),
|
||||||
|
"State {}".format(i),
|
||||||
|
{'attribute_test': i})
|
||||||
|
|
||||||
|
mock_state_change_event(self.hass, state)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
if i < 5:
|
||||||
|
states.append(state)
|
||||||
|
|
||||||
|
if i == 4:
|
||||||
|
time.sleep(1)
|
||||||
|
point = dt_util.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
states,
|
||||||
|
sorted(
|
||||||
|
history.get_states(point), key=lambda state: state.entity_id))
|
||||||
|
|
||||||
|
# Test get_state here because we have a DB setup
|
||||||
|
self.assertEqual(
|
||||||
|
states[0], history.get_state(point, states[0].entity_id))
|
||||||
|
|
||||||
|
def test_state_changes_during_period(self):
|
||||||
|
self.init_recorder()
|
||||||
|
entity_id = 'media_player.test'
|
||||||
|
|
||||||
|
def set_state(state):
|
||||||
|
self.hass.states.set(entity_id, state)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
return self.hass.states.get(entity_id)
|
||||||
|
|
||||||
|
set_state('idle')
|
||||||
|
set_state('YouTube')
|
||||||
|
|
||||||
|
start = dt_util.utcnow()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
states = [
|
||||||
|
set_state('idle'),
|
||||||
|
set_state('Netflix'),
|
||||||
|
set_state('Plex'),
|
||||||
|
set_state('YouTube'),
|
||||||
|
]
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
end = dt_util.utcnow()
|
||||||
|
|
||||||
|
set_state('Netflix')
|
||||||
|
set_state('Plex')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
{entity_id: states},
|
||||||
|
history.state_changes_during_period(start, end, entity_id))
|
98
tests/test_component_logbook.py
Normal file
98
tests/test_component_logbook.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_logbook
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the logbook component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import unittest
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
|
from homeassistant.const import (
|
||||||
|
EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.components import logbook, http
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant
|
||||||
|
|
||||||
|
SERVER_PORT = 8127
|
||||||
|
|
||||||
|
|
||||||
|
class TestComponentHistory(unittest.TestCase):
|
||||||
|
""" Tests homeassistant.components.history module. """
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
""" Test setup method. """
|
||||||
|
try:
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
http.setup(hass, {
|
||||||
|
http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||||
|
self.assertTrue(logbook.setup(hass, {}))
|
||||||
|
finally:
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
def test_humanify_filter_sensor(self):
|
||||||
|
""" Test humanify filter too frequent sensor values. """
|
||||||
|
entity_id = 'sensor.bla'
|
||||||
|
|
||||||
|
pointA = dt_util.strip_microseconds(dt_util.utcnow().replace(minute=2))
|
||||||
|
pointB = pointA.replace(minute=5)
|
||||||
|
pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
|
||||||
|
|
||||||
|
eventA = self.create_state_changed_event(pointA, entity_id, 10)
|
||||||
|
eventB = self.create_state_changed_event(pointB, entity_id, 20)
|
||||||
|
eventC = self.create_state_changed_event(pointC, entity_id, 30)
|
||||||
|
|
||||||
|
entries = list(logbook.humanify((eventA, eventB, eventC)))
|
||||||
|
|
||||||
|
self.assertEqual(2, len(entries))
|
||||||
|
self.assert_entry(
|
||||||
|
entries[0], pointB, 'bla', domain='sensor', entity_id=entity_id)
|
||||||
|
|
||||||
|
self.assert_entry(
|
||||||
|
entries[1], pointC, 'bla', domain='sensor', entity_id=entity_id)
|
||||||
|
|
||||||
|
def test_home_assistant_start_stop_grouped(self):
|
||||||
|
""" Tests if home assistant start and stop events are grouped if
|
||||||
|
occuring in the same minute. """
|
||||||
|
entries = list(logbook.humanify((
|
||||||
|
ha.Event(EVENT_HOMEASSISTANT_STOP),
|
||||||
|
ha.Event(EVENT_HOMEASSISTANT_START),
|
||||||
|
)))
|
||||||
|
|
||||||
|
self.assertEqual(1, len(entries))
|
||||||
|
self.assert_entry(
|
||||||
|
entries[0], name='Home Assistant', message='restarted',
|
||||||
|
domain=ha.DOMAIN)
|
||||||
|
|
||||||
|
def assert_entry(self, entry, when=None, name=None, message=None,
|
||||||
|
domain=None, entity_id=None):
|
||||||
|
""" Asserts an entry is what is expected """
|
||||||
|
if when:
|
||||||
|
self.assertEqual(when, entry.when)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
self.assertEqual(name, entry.name)
|
||||||
|
|
||||||
|
if message:
|
||||||
|
self.assertEqual(message, entry.message)
|
||||||
|
|
||||||
|
if domain:
|
||||||
|
self.assertEqual(domain, entry.domain)
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
self.assertEqual(entity_id, entry.entity_id)
|
||||||
|
|
||||||
|
def create_state_changed_event(self, event_time_fired, entity_id, state):
|
||||||
|
""" Create state changed event. """
|
||||||
|
|
||||||
|
# Logbook only cares about state change events that
|
||||||
|
# contain an old state but will not actually act on it.
|
||||||
|
state = ha.State(entity_id, state).as_dict()
|
||||||
|
|
||||||
|
return ha.Event(EVENT_STATE_CHANGED, {
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'old_state': state,
|
||||||
|
'new_state': state,
|
||||||
|
}, time_fired=event_time_fired)
|
70
tests/test_component_recorder.py
Normal file
70
tests/test_component_recorder.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_recorder
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests Recorder component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from homeassistant.const import MATCH_ALL
|
||||||
|
from homeassistant.components import recorder
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant
|
||||||
|
|
||||||
|
|
||||||
|
class TestRecorder(unittest.TestCase):
|
||||||
|
""" Test the chromecast module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
recorder.setup(self.hass, {})
|
||||||
|
self.hass.start()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
os.remove(self.hass.config.path(recorder.DB_FILE))
|
||||||
|
|
||||||
|
def test_saving_state(self):
|
||||||
|
""" Tests saving and restoring a state. """
|
||||||
|
entity_id = 'test.recorder'
|
||||||
|
state = 'restoring_from_db'
|
||||||
|
attributes = {'test_attr': 5, 'test_attr_10': 'nice'}
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, state, attributes)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
states = recorder.query_states('SELECT * FROM states')
|
||||||
|
|
||||||
|
self.assertEqual(1, len(states))
|
||||||
|
self.assertEqual(self.hass.states.get(entity_id), states[0])
|
||||||
|
|
||||||
|
def test_saving_event(self):
|
||||||
|
""" Tests saving and restoring an event. """
|
||||||
|
event_type = 'EVENT_TEST'
|
||||||
|
event_data = {'test_attr': 5, 'test_attr_10': 'nice'}
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def event_listener(event):
|
||||||
|
""" Records events from eventbus. """
|
||||||
|
if event.event_type == event_type:
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
self.hass.bus.listen(MATCH_ALL, event_listener)
|
||||||
|
|
||||||
|
self.hass.bus.fire(event_type, event_data)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
db_events = recorder.query_events(
|
||||||
|
'SELECT * FROM events WHERE event_type = ?', (event_type, ))
|
||||||
|
|
||||||
|
self.assertEqual(events, db_events)
|
@ -6,11 +6,12 @@ Tests Sun component.
|
|||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
import unittest
|
import unittest
|
||||||
import datetime as dt
|
from datetime import timedelta
|
||||||
|
|
||||||
import ephem
|
import ephem
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.sun as sun
|
import homeassistant.components.sun as sun
|
||||||
|
|
||||||
|
|
||||||
@ -42,22 +43,20 @@ class TestSun(unittest.TestCase):
|
|||||||
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
|
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
|
||||||
observer.long = '117.22743' # pylint: disable=assigning-non-slot
|
observer.long = '117.22743' # pylint: disable=assigning-non-slot
|
||||||
|
|
||||||
utc_now = dt.datetime.utcnow()
|
utc_now = dt_util.utcnow()
|
||||||
body_sun = ephem.Sun() # pylint: disable=no-member
|
body_sun = ephem.Sun() # pylint: disable=no-member
|
||||||
next_rising_dt = ephem.localtime(
|
next_rising_dt = observer.next_rising(
|
||||||
observer.next_rising(body_sun, start=utc_now))
|
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
next_setting_dt = ephem.localtime(
|
next_setting_dt = observer.next_setting(
|
||||||
observer.next_setting(body_sun, start=utc_now))
|
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
# Home Assistant strips out microseconds
|
# Home Assistant strips out microseconds
|
||||||
# strip it out of the datetime objects
|
# strip it out of the datetime objects
|
||||||
next_rising_dt = next_rising_dt - dt.timedelta(
|
next_rising_dt = dt_util.strip_microseconds(next_rising_dt)
|
||||||
microseconds=next_rising_dt.microsecond)
|
next_setting_dt = dt_util.strip_microseconds(next_setting_dt)
|
||||||
next_setting_dt = next_setting_dt - dt.timedelta(
|
|
||||||
microseconds=next_setting_dt.microsecond)
|
|
||||||
|
|
||||||
self.assertEqual(next_rising_dt, sun.next_rising(self.hass))
|
self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass))
|
||||||
self.assertEqual(next_setting_dt, sun.next_setting(self.hass))
|
self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass))
|
||||||
|
|
||||||
# Point it at a state without the proper attributes
|
# Point it at a state without the proper attributes
|
||||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
||||||
@ -84,7 +83,7 @@ class TestSun(unittest.TestCase):
|
|||||||
self.assertIsNotNone(test_time)
|
self.assertIsNotNone(test_time)
|
||||||
|
|
||||||
self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
|
self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
|
||||||
{ha.ATTR_NOW: test_time + dt.timedelta(seconds=5)})
|
{ha.ATTR_NOW: test_time + timedelta(seconds=5)})
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
180
tests/test_config.py
Normal file
180
tests/test_config.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
"""
|
||||||
|
tests.test_config
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests config utils.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import unittest
|
||||||
|
import unittest.mock as mock
|
||||||
|
import os
|
||||||
|
|
||||||
|
from homeassistant import DOMAIN, HomeAssistantError
|
||||||
|
import homeassistant.util as util
|
||||||
|
import homeassistant.config as config_util
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||||
|
CONF_TIME_ZONE)
|
||||||
|
|
||||||
|
from helpers import get_test_config_dir
|
||||||
|
|
||||||
|
CONFIG_DIR = get_test_config_dir()
|
||||||
|
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
||||||
|
CONF_PATH = os.path.join(CONFIG_DIR, config_util.CONF_CONFIG_FILE)
|
||||||
|
|
||||||
|
|
||||||
|
def create_file(path):
|
||||||
|
""" Creates an empty file. """
|
||||||
|
with open(path, 'w'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def mock_detect_location_info():
|
||||||
|
""" Mock implementation of util.detect_location_info. """
|
||||||
|
return util.LocationInfo(
|
||||||
|
ip='1.1.1.1',
|
||||||
|
country_code='US',
|
||||||
|
country_name='United States',
|
||||||
|
region_code='CA',
|
||||||
|
region_name='California',
|
||||||
|
city='San Diego',
|
||||||
|
zip_code='92122',
|
||||||
|
time_zone='America/Los_Angeles',
|
||||||
|
latitude='2.0',
|
||||||
|
longitude='1.0',
|
||||||
|
use_fahrenheit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(unittest.TestCase):
|
||||||
|
""" Test the config utils. """
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Clean up. """
|
||||||
|
for path in (YAML_PATH, CONF_PATH):
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
def test_create_default_config(self):
|
||||||
|
""" Test creationg of default config. """
|
||||||
|
|
||||||
|
config_util.create_default_config(CONFIG_DIR, False)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.isfile(YAML_PATH))
|
||||||
|
|
||||||
|
def test_find_config_file_yaml(self):
|
||||||
|
""" Test if it finds a YAML config file. """
|
||||||
|
|
||||||
|
create_file(YAML_PATH)
|
||||||
|
|
||||||
|
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||||
|
|
||||||
|
def test_find_config_file_conf(self):
|
||||||
|
""" Test if it finds the old CONF config file. """
|
||||||
|
|
||||||
|
create_file(CONF_PATH)
|
||||||
|
|
||||||
|
self.assertEqual(CONF_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||||
|
|
||||||
|
def test_find_config_file_prefers_yaml_over_conf(self):
|
||||||
|
""" Test if find config prefers YAML over CONF if both exist. """
|
||||||
|
|
||||||
|
create_file(YAML_PATH)
|
||||||
|
create_file(CONF_PATH)
|
||||||
|
|
||||||
|
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR))
|
||||||
|
|
||||||
|
def test_ensure_config_exists_creates_config(self):
|
||||||
|
""" Test that calling ensure_config_exists creates a new config file if
|
||||||
|
none exists. """
|
||||||
|
|
||||||
|
config_util.ensure_config_exists(CONFIG_DIR, False)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.isfile(YAML_PATH))
|
||||||
|
|
||||||
|
def test_ensure_config_exists_uses_existing_config(self):
|
||||||
|
""" Test that calling ensure_config_exists uses existing config. """
|
||||||
|
|
||||||
|
create_file(YAML_PATH)
|
||||||
|
config_util.ensure_config_exists(CONFIG_DIR, False)
|
||||||
|
|
||||||
|
with open(YAML_PATH) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# File created with create_file are empty
|
||||||
|
self.assertEqual('', content)
|
||||||
|
|
||||||
|
def test_load_yaml_config_converts_empty_files_to_dict(self):
|
||||||
|
""" Test that loading an empty file returns an empty dict. """
|
||||||
|
create_file(YAML_PATH)
|
||||||
|
|
||||||
|
self.assertIsInstance(
|
||||||
|
config_util.load_yaml_config_file(YAML_PATH), dict)
|
||||||
|
|
||||||
|
def test_load_yaml_config_raises_error_if_not_dict(self):
|
||||||
|
""" Test error raised when YAML file is not a dict. """
|
||||||
|
with open(YAML_PATH, 'w') as f:
|
||||||
|
f.write('5')
|
||||||
|
|
||||||
|
with self.assertRaises(HomeAssistantError):
|
||||||
|
config_util.load_yaml_config_file(YAML_PATH)
|
||||||
|
|
||||||
|
def test_load_yaml_config_raises_error_if_malformed_yaml(self):
|
||||||
|
""" Test error raised if invalid YAML. """
|
||||||
|
with open(YAML_PATH, 'w') as f:
|
||||||
|
f.write(':')
|
||||||
|
|
||||||
|
with self.assertRaises(HomeAssistantError):
|
||||||
|
config_util.load_yaml_config_file(YAML_PATH)
|
||||||
|
|
||||||
|
def test_load_config_loads_yaml_config(self):
|
||||||
|
""" Test correct YAML config loading. """
|
||||||
|
with open(YAML_PATH, 'w') as f:
|
||||||
|
f.write('hello: world')
|
||||||
|
|
||||||
|
self.assertEqual({'hello': 'world'},
|
||||||
|
config_util.load_config_file(YAML_PATH))
|
||||||
|
|
||||||
|
def test_load_config_loads_conf_config(self):
|
||||||
|
""" Test correct YAML config loading. """
|
||||||
|
create_file(CONF_PATH)
|
||||||
|
|
||||||
|
self.assertEqual({}, config_util.load_config_file(CONF_PATH))
|
||||||
|
|
||||||
|
def test_conf_config_file(self):
|
||||||
|
""" Test correct CONF config loading. """
|
||||||
|
with open(CONF_PATH, 'w') as f:
|
||||||
|
f.write('[ha]\ntime_zone=America/Los_Angeles')
|
||||||
|
|
||||||
|
self.assertEqual({'ha': {'time_zone': 'America/Los_Angeles'}},
|
||||||
|
config_util.load_conf_config_file(CONF_PATH))
|
||||||
|
|
||||||
|
def test_create_default_config_detect_location(self):
|
||||||
|
""" Test that detect location sets the correct config keys. """
|
||||||
|
with mock.patch('homeassistant.util.detect_location_info',
|
||||||
|
mock_detect_location_info):
|
||||||
|
config_util.ensure_config_exists(CONFIG_DIR)
|
||||||
|
|
||||||
|
config = config_util.load_config_file(YAML_PATH)
|
||||||
|
|
||||||
|
self.assertIn(DOMAIN, config)
|
||||||
|
|
||||||
|
ha_conf = config[DOMAIN]
|
||||||
|
|
||||||
|
expected_values = {
|
||||||
|
CONF_LATITUDE: 2.0,
|
||||||
|
CONF_LONGITUDE: 1.0,
|
||||||
|
CONF_TEMPERATURE_UNIT: 'F',
|
||||||
|
CONF_NAME: 'Home',
|
||||||
|
CONF_TIME_ZONE: 'America/Los_Angeles'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_values, ha_conf)
|
||||||
|
|
||||||
|
def test_create_default_config_returns_none_if_write_error(self):
|
||||||
|
"""
|
||||||
|
Test that writing default config to non existing folder returns None.
|
||||||
|
"""
|
||||||
|
self.assertIsNone(
|
||||||
|
config_util.create_default_config(
|
||||||
|
os.path.join(CONFIG_DIR, 'non_existing_dir/'), False))
|
@ -30,7 +30,11 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
|
|
||||||
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.hass.stop()
|
try:
|
||||||
|
self.hass.stop()
|
||||||
|
except ha.HomeAssistantError:
|
||||||
|
# Already stopped after the block till stopped test
|
||||||
|
pass
|
||||||
|
|
||||||
def test_get_config_path(self):
|
def test_get_config_path(self):
|
||||||
""" Test get_config_path method. """
|
""" Test get_config_path method. """
|
||||||
@ -72,7 +76,7 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
|
|
||||||
runs = []
|
runs = []
|
||||||
|
|
||||||
self.hass.track_point_in_time(
|
self.hass.track_point_in_utc_time(
|
||||||
lambda x: runs.append(1), birthday_paulus)
|
lambda x: runs.append(1), birthday_paulus)
|
||||||
|
|
||||||
self._send_time_changed(before_birthday)
|
self._send_time_changed(before_birthday)
|
||||||
@ -88,7 +92,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_time(
|
self.hass.track_point_in_utc_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)
|
||||||
|
@ -25,7 +25,8 @@ class TestHelpersEntity(unittest.TestCase):
|
|||||||
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.hass.stop()
|
self.hass.stop()
|
||||||
entity.Entity.overwrite_hidden(self.entity.entity_id, None)
|
entity.Entity.overwrite_attribute(self.entity.entity_id,
|
||||||
|
[ATTR_HIDDEN], [None])
|
||||||
|
|
||||||
def test_default_hidden_not_in_attributes(self):
|
def test_default_hidden_not_in_attributes(self):
|
||||||
""" Test that the default hidden property is set to False. """
|
""" Test that the default hidden property is set to False. """
|
||||||
@ -43,7 +44,8 @@ class TestHelpersEntity(unittest.TestCase):
|
|||||||
|
|
||||||
def test_overwriting_hidden_property_to_true(self):
|
def test_overwriting_hidden_property_to_true(self):
|
||||||
""" Test we can overwrite hidden property to True. """
|
""" Test we can overwrite hidden property to True. """
|
||||||
entity.Entity.overwrite_hidden(self.entity.entity_id, True)
|
entity.Entity.overwrite_attribute(self.entity.entity_id,
|
||||||
|
[ATTR_HIDDEN], [True])
|
||||||
self.entity.update_ha_state()
|
self.entity.update_ha_state()
|
||||||
|
|
||||||
state = self.hass.states.get(self.entity.entity_id)
|
state = self.hass.states.get(self.entity.entity_id)
|
||||||
@ -51,7 +53,8 @@ class TestHelpersEntity(unittest.TestCase):
|
|||||||
|
|
||||||
def test_overwriting_hidden_property_to_false(self):
|
def test_overwriting_hidden_property_to_false(self):
|
||||||
""" Test we can overwrite hidden property to True. """
|
""" Test we can overwrite hidden property to True. """
|
||||||
entity.Entity.overwrite_hidden(self.entity.entity_id, False)
|
entity.Entity.overwrite_attribute(self.entity.entity_id,
|
||||||
|
[ATTR_HIDDEN], [False])
|
||||||
self.entity.hidden = True
|
self.entity.hidden = True
|
||||||
self.entity.update_ha_state()
|
self.entity.update_ha_state()
|
||||||
|
|
||||||
|
@ -35,17 +35,6 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual("Test_More", util.slugify("Test More"))
|
self.assertEqual("Test_More", util.slugify("Test More"))
|
||||||
self.assertEqual("Test_More", util.slugify("Test_(More)"))
|
self.assertEqual("Test_More", util.slugify("Test_(More)"))
|
||||||
|
|
||||||
def test_datetime_to_str(self):
|
|
||||||
""" Test datetime_to_str. """
|
|
||||||
self.assertEqual("12:00:00 09-07-1986",
|
|
||||||
util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0)))
|
|
||||||
|
|
||||||
def test_str_to_datetime(self):
|
|
||||||
""" Test str_to_datetime. """
|
|
||||||
self.assertEqual(datetime(1986, 7, 9, 12, 0, 0),
|
|
||||||
util.str_to_datetime("12:00:00 09-07-1986"))
|
|
||||||
self.assertIsNone(util.str_to_datetime("not a datetime string"))
|
|
||||||
|
|
||||||
def test_split_entity_id(self):
|
def test_split_entity_id(self):
|
||||||
""" Test split_entity_id. """
|
""" Test split_entity_id. """
|
||||||
self.assertEqual(['domain', 'object_id'],
|
self.assertEqual(['domain', 'object_id'],
|
||||||
|
137
tests/test_util_dt.py
Normal file
137
tests/test_util_dt.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
tests.test_util
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests Home Assistant date util methods.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
TEST_TIME_ZONE = 'America/Los_Angeles'
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateUtil(unittest.TestCase):
|
||||||
|
""" Tests util date methods. """
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
dt_util.set_default_time_zone(self.orig_default_time_zone)
|
||||||
|
|
||||||
|
def test_get_time_zone_retrieves_valid_time_zone(self):
|
||||||
|
""" Test getting a time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone(TEST_TIME_ZONE)
|
||||||
|
|
||||||
|
self.assertIsNotNone(time_zone)
|
||||||
|
self.assertEqual(TEST_TIME_ZONE, time_zone.zone)
|
||||||
|
|
||||||
|
def test_get_time_zone_returns_none_for_garbage_time_zone(self):
|
||||||
|
""" Test getting a non existing time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone("Non existing time zone")
|
||||||
|
|
||||||
|
self.assertIsNone(time_zone)
|
||||||
|
|
||||||
|
def test_set_default_time_zone(self):
|
||||||
|
""" Test setting default time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone(TEST_TIME_ZONE)
|
||||||
|
|
||||||
|
dt_util.set_default_time_zone(time_zone)
|
||||||
|
|
||||||
|
# We cannot compare the timezones directly because of DST
|
||||||
|
self.assertEqual(time_zone.zone, dt_util.now().tzinfo.zone)
|
||||||
|
|
||||||
|
def test_utcnow(self):
|
||||||
|
""" Test the UTC now method. """
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
dt_util.utcnow().replace(tzinfo=None),
|
||||||
|
datetime.utcnow(),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_now(self):
|
||||||
|
""" Test the now method. """
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
dt_util.as_utc(dt_util.now()).replace(tzinfo=None),
|
||||||
|
datetime.utcnow(),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_as_utc_with_naive_object(self):
|
||||||
|
utcnow = datetime.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(utcnow,
|
||||||
|
dt_util.as_utc(utcnow).replace(tzinfo=None))
|
||||||
|
|
||||||
|
def test_as_utc_with_utc_object(self):
|
||||||
|
utcnow = dt_util.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(utcnow, dt_util.as_utc(utcnow))
|
||||||
|
|
||||||
|
def test_as_utc_with_local_object(self):
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
localnow = dt_util.now()
|
||||||
|
|
||||||
|
utcnow = dt_util.as_utc(localnow)
|
||||||
|
|
||||||
|
self.assertEqual(localnow, utcnow)
|
||||||
|
self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo)
|
||||||
|
|
||||||
|
def test_as_local_with_naive_object(self):
|
||||||
|
now = dt_util.now()
|
||||||
|
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
now, dt_util.as_local(datetime.utcnow()),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_as_local_with_local_object(self):
|
||||||
|
now = dt_util.now()
|
||||||
|
|
||||||
|
self.assertEqual(now, now)
|
||||||
|
|
||||||
|
def test_as_local_with_utc_object(self):
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
utcnow = dt_util.utcnow()
|
||||||
|
localnow = dt_util.as_local(utcnow)
|
||||||
|
|
||||||
|
self.assertEqual(localnow, utcnow)
|
||||||
|
self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo)
|
||||||
|
|
||||||
|
def test_utc_from_timestamp(self):
|
||||||
|
""" Test utc_from_timestamp method. """
|
||||||
|
self.assertEqual(
|
||||||
|
datetime(1986, 7, 9, tzinfo=dt_util.UTC),
|
||||||
|
dt_util.utc_from_timestamp(521251200))
|
||||||
|
|
||||||
|
def test_datetime_to_str(self):
|
||||||
|
""" Test datetime_to_str. """
|
||||||
|
self.assertEqual(
|
||||||
|
"12:00:00 09-07-1986",
|
||||||
|
dt_util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0)))
|
||||||
|
|
||||||
|
def test_datetime_to_local_str(self):
|
||||||
|
""" Test datetime_to_local_str. """
|
||||||
|
self.assertEqual(
|
||||||
|
dt_util.datetime_to_str(dt_util.now()),
|
||||||
|
dt_util.datetime_to_local_str(dt_util.utcnow()))
|
||||||
|
|
||||||
|
def test_str_to_datetime_converts_correctly(self):
|
||||||
|
""" Test str_to_datetime converts strings. """
|
||||||
|
self.assertEqual(
|
||||||
|
datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC),
|
||||||
|
dt_util.str_to_datetime("12:00:00 09-07-1986"))
|
||||||
|
|
||||||
|
def test_str_to_datetime_returns_none_for_incorrect_format(self):
|
||||||
|
""" Test str_to_datetime returns None if incorrect format. """
|
||||||
|
self.assertIsNone(dt_util.str_to_datetime("not a datetime string"))
|
||||||
|
|
||||||
|
def test_strip_microseconds(self):
|
||||||
|
test_time = datetime(2015, 1, 1, microsecond=5000)
|
||||||
|
|
||||||
|
self.assertNotEqual(0, test_time.microsecond)
|
||||||
|
self.assertEqual(0, dt_util.strip_microseconds(test_time).microsecond)
|
Loading…
x
Reference in New Issue
Block a user