mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +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
|
||||
|
||||
omit =
|
||||
homeassistant/__main__.py
|
||||
|
||||
homeassistant/external/*
|
||||
|
||||
# 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/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
homeassistant/components/*/tellstick.py
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/switch/wemo.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/browser.py
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/device_tracker/luci.py
|
||||
homeassistant/components/device_tracker/tomato.py
|
||||
homeassistant/components/device_tracker/netgear.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/isy994.py
|
||||
homeassistant/components/light/isy994.py
|
||||
homeassistant/components/switch/isy994.py
|
||||
homeassistant/components/sensor/isy994.py
|
||||
homeassistant/components/switch/wemo.py
|
||||
homeassistant/components/thermostat/nest.py
|
||||
|
||||
|
||||
[report]
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -19,3 +19,6 @@
|
||||
[submodule "homeassistant/external/nzbclients"]
|
||||
path = homeassistant/external/nzbclients
|
||||
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'
|
||||
|
||||
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
|
||||
host: 192.168.1.1
|
||||
username: admin
|
||||
@ -65,7 +66,7 @@ device_sun_light_trigger:
|
||||
# Optional: disable lights being turned off when everybody leaves the house
|
||||
# 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)
|
||||
group:
|
||||
living_room:
|
||||
|
@ -22,7 +22,7 @@ DOMAIN = "example"
|
||||
|
||||
# List of component names (string) your component depends upon
|
||||
# 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']
|
||||
|
||||
# Configuration key for the entity id we are targetting
|
||||
@ -115,5 +115,5 @@ def setup(hass, config):
|
||||
# Register our service with HASS.
|
||||
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
|
||||
|
@ -12,11 +12,8 @@ import logging
|
||||
import threading
|
||||
import enum
|
||||
import re
|
||||
import datetime as dt
|
||||
import functools as ft
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||
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,
|
||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as date_util
|
||||
|
||||
DOMAIN = "homeassistant"
|
||||
|
||||
@ -109,7 +107,20 @@ class HomeAssistant(object):
|
||||
|
||||
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)
|
||||
@ -135,11 +146,19 @@ class HomeAssistant(object):
|
||||
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
||||
return point_in_time_listener
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_utc_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None):
|
||||
""" Adds a listener that will fire if time matches a pattern. """
|
||||
self.track_time_change(
|
||||
action, year, month, day, hour, minute, second, utc=True)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def track_time_change(self, action,
|
||||
year=None, month=None, day=None,
|
||||
hour=None, minute=None, second=None):
|
||||
""" Adds a listener that will fire if time matches a pattern. """
|
||||
hour=None, minute=None, second=None, utc=False):
|
||||
""" Adds a listener that will fire if UTC time matches a pattern. """
|
||||
|
||||
# We do not have to wrap the function with time pattern matching logic
|
||||
# if no pattern given
|
||||
@ -155,6 +174,9 @@ class HomeAssistant(object):
|
||||
""" Listens for matching time_changed events. """
|
||||
now = event.data[ATTR_NOW]
|
||||
|
||||
if not utc:
|
||||
now = date_util.as_local(now)
|
||||
|
||||
mat = _matcher
|
||||
|
||||
if mat(now.year, year) and \
|
||||
@ -305,7 +327,7 @@ def create_worker_pool():
|
||||
|
||||
for start, job in current_jobs:
|
||||
_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)
|
||||
|
||||
@ -333,7 +355,7 @@ class Event(object):
|
||||
self.data = data or {}
|
||||
self.origin = origin
|
||||
self.time_fired = util.strip_microseconds(
|
||||
time_fired or dt.datetime.now())
|
||||
time_fired or date_util.utcnow())
|
||||
|
||||
def as_dict(self):
|
||||
""" Returns a dict representation of this Event. """
|
||||
@ -341,7 +363,7 @@ class Event(object):
|
||||
'event_type': self.event_type,
|
||||
'data': dict(self.data),
|
||||
'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):
|
||||
@ -354,6 +376,13 @@ class Event(object):
|
||||
return "<Event {}[{}]>".format(self.event_type,
|
||||
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 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):
|
||||
""" Fire an event. """
|
||||
if not self._pool.running:
|
||||
raise HomeAssistantError('Home Assistant has shut down.')
|
||||
|
||||
with self._lock:
|
||||
# Copy the list of the current listeners because some listeners
|
||||
# remove themselves as a listener while being executed which
|
||||
@ -474,13 +506,14 @@ class State(object):
|
||||
self.entity_id = entity_id.lower()
|
||||
self.state = state
|
||||
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
|
||||
# state == State.from_dict(state.as_dict())
|
||||
# This behavior occurs because to_dict uses datetime_to_str
|
||||
# which does not preserve microseconds
|
||||
self.last_changed = util.strip_microseconds(
|
||||
self.last_changed = date_util.strip_microseconds(
|
||||
last_changed or self.last_updated)
|
||||
|
||||
@property
|
||||
@ -512,8 +545,8 @@ class State(object):
|
||||
return {'entity_id': self.entity_id,
|
||||
'state': self.state,
|
||||
'attributes': self.attributes,
|
||||
'last_changed': util.datetime_to_str(self.last_changed),
|
||||
'last_updated': util.datetime_to_str(self.last_updated)}
|
||||
'last_changed': date_util.datetime_to_str(self.last_changed),
|
||||
'last_updated': date_util.datetime_to_str(self.last_updated)}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, json_dict):
|
||||
@ -528,12 +561,12 @@ class State(object):
|
||||
last_changed = json_dict.get('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')
|
||||
|
||||
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'],
|
||||
json_dict.get('attributes'), last_changed, last_updated)
|
||||
@ -550,7 +583,7 @@ class State(object):
|
||||
|
||||
return "<state {}={}{} @ {}>".format(
|
||||
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):
|
||||
@ -587,7 +620,7 @@ class StateMachine(object):
|
||||
"""
|
||||
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:
|
||||
return [state for state in self._states.values()
|
||||
@ -849,7 +882,7 @@ class Timer(threading.Thread):
|
||||
|
||||
last_fired_on_second = -1
|
||||
|
||||
calc_now = dt.datetime.now
|
||||
calc_now = date_util.utcnow
|
||||
interval = self.interval
|
||||
|
||||
while not self._stop_event.isSet():
|
||||
@ -875,7 +908,13 @@ class Timer(threading.Thread):
|
||||
|
||||
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):
|
||||
@ -898,44 +937,9 @@ class Config(object):
|
||||
# Directory that holds the configuration
|
||||
self.config_dir = os.path.join(os.getcwd(), 'config')
|
||||
|
||||
def auto_detect(self):
|
||||
""" 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):
|
||||
def path(self, *path):
|
||||
""" 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):
|
||||
""" Converts temperature to user preferred unit if set. """
|
||||
@ -955,6 +959,17 @@ class Config(object):
|
||||
# Could not convert value to float
|
||||
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):
|
||||
""" General Home Assistant exception occured. """
|
||||
|
@ -7,6 +7,14 @@ import argparse
|
||||
import importlib
|
||||
|
||||
|
||||
# Home Assistant dependencies, mapped module -> package name
|
||||
DEPENDENCIES = {
|
||||
'requests': 'requests',
|
||||
'yaml': 'pyyaml',
|
||||
'pytz': 'pytz',
|
||||
}
|
||||
|
||||
|
||||
def validate_python():
|
||||
""" Validate we're running the right Python version. """
|
||||
major, minor = sys.version_info[:2]
|
||||
@ -20,13 +28,13 @@ def validate_dependencies():
|
||||
""" Validate all dependencies that HA uses. """
|
||||
import_fail = False
|
||||
|
||||
for module in ['requests']:
|
||||
for module, name in DEPENDENCIES.items():
|
||||
try:
|
||||
importlib.import_module(module)
|
||||
except ImportError:
|
||||
import_fail = True
|
||||
print(
|
||||
'Fatal Error: Unable to find dependency {}'.format(module))
|
||||
'Fatal Error: Unable to find dependency {}'.format(name))
|
||||
|
||||
if import_fail:
|
||||
print(("Install dependencies by running: "
|
||||
@ -72,24 +80,13 @@ def ensure_config_path(config_dir):
|
||||
'directory {} ').format(config_dir))
|
||||
sys.exit()
|
||||
|
||||
# Try to use yaml configuration first
|
||||
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')
|
||||
import homeassistant.config as config_util
|
||||
|
||||
# Ensure a config file exists to make first time usage easier
|
||||
if not os.path.isfile(config_path):
|
||||
config_path = os.path.join(config_dir, 'configuration.yaml')
|
||||
try:
|
||||
with open(config_path, 'w') as conf:
|
||||
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()
|
||||
config_path = config_util.ensure_config_exists(config_dir)
|
||||
|
||||
if config_path is None:
|
||||
print('Error getting configuration path')
|
||||
sys.exit()
|
||||
|
||||
return config_path
|
||||
|
||||
|
@ -10,25 +10,27 @@ start by calling homeassistant.start_home_assistant(bus)
|
||||
"""
|
||||
|
||||
import os
|
||||
import configparser
|
||||
import yaml
|
||||
import io
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
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.components as core_components
|
||||
import homeassistant.components.group as group
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (
|
||||
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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_COMPONENT = "component"
|
||||
ATTR_COMPONENT = 'component'
|
||||
|
||||
PLATFORM_FORMAT = '{}.{}'
|
||||
|
||||
|
||||
def setup_component(hass, domain, config=None):
|
||||
@ -67,7 +69,7 @@ def _setup_component(hass, domain, config):
|
||||
|
||||
if missing_deps:
|
||||
_LOGGER.error(
|
||||
"Not initializing %s because not all dependencies loaded: %s",
|
||||
'Not initializing %s because not all dependencies loaded: %s',
|
||||
domain, ", ".join(missing_deps))
|
||||
|
||||
return False
|
||||
@ -87,14 +89,42 @@ def _setup_component(hass, domain, config):
|
||||
return True
|
||||
|
||||
else:
|
||||
_LOGGER.error("component %s failed to initialize", domain)
|
||||
_LOGGER.error('component %s failed to initialize', domain)
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
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 core_components.setup(hass, config):
|
||||
_LOGGER.error("Home Assistant core failed to initialize. "
|
||||
"Further initialization aborted.")
|
||||
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||
'Further initialization aborted.')
|
||||
|
||||
return hass
|
||||
|
||||
_LOGGER.info("Home Assistant core initialized")
|
||||
_LOGGER.info('Home Assistant core initialized')
|
||||
|
||||
# Setup the 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
|
||||
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||
|
||||
config_dict = {}
|
||||
# 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
|
||||
config_dict = config_util.load_config_file(config_path)
|
||||
|
||||
return from_config_dict(config_dict, hass)
|
||||
|
||||
@ -177,7 +188,7 @@ def enable_logging(hass):
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 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)
|
||||
|
||||
# Check if we can write to the error log if it exists or that
|
||||
@ -196,30 +207,73 @@ def enable_logging(hass):
|
||||
|
||||
else:
|
||||
_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):
|
||||
""" 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'),
|
||||
(CONF_LONGITUDE, 'longitude'),
|
||||
(CONF_NAME, 'location_name'),
|
||||
(CONF_TIME_ZONE, 'time_zone')):
|
||||
(CONF_NAME, 'location_name')):
|
||||
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():
|
||||
Entity.overwrite_hidden(entity_id, hidden == 'hide')
|
||||
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||
|
||||
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:
|
||||
unit = config[CONF_TEMPERATURE_UNIT]
|
||||
|
||||
if unit == 'C':
|
||||
hass.config.temperature_unit = TEMP_CELCIUS
|
||||
hac.temperature_unit = TEMP_CELCIUS
|
||||
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):
|
||||
|
@ -15,6 +15,7 @@ import homeassistant.remote as rem
|
||||
from homeassistant.const import (
|
||||
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_CONFIG, URL_API_BOOTSTRAP,
|
||||
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
|
||||
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
@ -42,6 +43,13 @@ def setup(hass, config):
|
||||
# /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
|
||||
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
""" Returns a dict containing all entity ids and their state. """
|
||||
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):
|
||||
""" Handles getting overview of event listeners. """
|
||||
handler.write_json([{"event": key, "listener_count": value}
|
||||
for key, value
|
||||
in handler.server.hass.bus.listeners.items()])
|
||||
handler.write_json(_events_json(handler.server.hass))
|
||||
|
||||
|
||||
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):
|
||||
""" Handles getting overview of services. """
|
||||
handler.write_json(
|
||||
[{"domain": key, "services": value}
|
||||
for key, value
|
||||
in handler.server.hass.services.services.items()])
|
||||
handler.write_json(_services_json(handler.server.hass))
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@ -312,3 +332,15 @@ def _handle_get_api_components(handler, path_match, data):
|
||||
""" Returns all the loaded 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sets up a demo environment that mimics interaction with devices
|
||||
Sets up a demo environment that mimics interaction with devices.
|
||||
"""
|
||||
import time
|
||||
|
||||
@ -32,7 +32,13 @@ def setup(hass, config):
|
||||
hass.states.set('a.Demo_Mode', 'Enabled')
|
||||
|
||||
# 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
|
||||
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.
|
||||
"""
|
||||
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 . import light, sun, device_tracker, group
|
||||
|
||||
@ -115,7 +116,7 @@ def setup(hass, config):
|
||||
new_state.state == STATE_HOME:
|
||||
|
||||
# These variables are needed for the elif check
|
||||
now = datetime.now()
|
||||
now = dt_util.now()
|
||||
start_point = calc_time_for_light_when_sunset()
|
||||
|
||||
# Do we need lights?
|
||||
|
@ -8,11 +8,12 @@ import logging
|
||||
import threading
|
||||
import os
|
||||
import csv
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers import validate_config
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
||||
@ -113,7 +114,7 @@ class DeviceTracker(object):
|
||||
""" Reload 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)
|
||||
|
||||
@ -125,7 +126,7 @@ class DeviceTracker(object):
|
||||
seconds = range(0, 60, 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,
|
||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||
@ -226,7 +227,7 @@ class DeviceTracker(object):
|
||||
self.untracked_devices.clear()
|
||||
|
||||
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
|
||||
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
|
||||
from datetime import timedelta
|
||||
import re
|
||||
@ -20,7 +51,7 @@ _DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@ -93,7 +124,7 @@ class DdWrtDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
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. """
|
||||
if not self.success_init:
|
||||
return False
|
||||
@ -111,8 +142,8 @@ class DdWrtDeviceScanner(object):
|
||||
self.last_results = []
|
||||
active_clients = data.get('active_wireless', None)
|
||||
if active_clients:
|
||||
# This is really lame, instead of using JSON the ddwrt UI
|
||||
# uses it's own data format for some reason and then
|
||||
# This is really lame, instead of using JSON the DD-WRT UI
|
||||
# uses its own data format for some reason and then
|
||||
# regex's out values so I guess I have to do the same,
|
||||
# LAME!!!
|
||||
|
||||
@ -132,7 +163,7 @@ class DdWrtDeviceScanner(object):
|
||||
return False
|
||||
|
||||
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:
|
||||
response = requests.get(
|
||||
url,
|
||||
@ -154,8 +185,7 @@ class DdWrtDeviceScanner(object):
|
||||
|
||||
|
||||
def _parse_ddwrt_response(data_str):
|
||||
""" Parse the awful DD-WRT data format, why didn't they use JSON????.
|
||||
This code is a python version of how they are parsing in the JS """
|
||||
""" Parse the DD-WRT data format. """
|
||||
return {
|
||||
key: val for key, val in _DDWRT_DATA_REGEX
|
||||
.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 json
|
||||
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
|
||||
from datetime import timedelta
|
||||
import threading
|
||||
@ -30,7 +61,7 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
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):
|
||||
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
|
||||
from datetime import timedelta, datetime
|
||||
from datetime import timedelta
|
||||
from collections import namedtuple
|
||||
import subprocess
|
||||
import re
|
||||
@ -8,6 +29,7 @@ import re
|
||||
from libnmap.process import NmapProcess
|
||||
from libnmap.parser import NmapParser, NmapParserException
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import CONF_HOSTS
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.util import Throttle, convert
|
||||
@ -36,7 +58,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
|
||||
|
||||
|
||||
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]
|
||||
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
out, _ = arp.communicate()
|
||||
@ -85,7 +107,7 @@ class NmapDeviceScanner(object):
|
||||
Returns True if successful, False otherwise. """
|
||||
try:
|
||||
results = NmapParser.parse(stdout)
|
||||
now = datetime.now()
|
||||
now = dt_util.now()
|
||||
self.last_results = []
|
||||
for host in results.hosts:
|
||||
if host.is_up():
|
||||
@ -119,7 +141,7 @@ class NmapDeviceScanner(object):
|
||||
options = "-F --host-timeout 5"
|
||||
exclude_targets = set()
|
||||
if self.home_interval:
|
||||
now = datetime.now()
|
||||
now = dt_util.now()
|
||||
for host in self.last_results:
|
||||
if host.last_update + self.home_interval > now:
|
||||
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 json
|
||||
from datetime import timedelta
|
||||
|
@ -1,11 +1,13 @@
|
||||
"""
|
||||
homeassistant.components.discovery
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Starts a service to scan in intervals for new devices.
|
||||
|
||||
Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
|
||||
|
||||
Knows which components handle certain types, will make sure they are
|
||||
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
|
||||
|
||||
"""
|
||||
import logging
|
||||
import threading
|
||||
|
@ -1,2 +1,2 @@
|
||||
""" 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"
|
||||
],
|
||||
"dependencies": {
|
||||
"webcomponentsjs": "Polymer/webcomponentsjs#~0.5.5",
|
||||
"webcomponentsjs": "Polymer/webcomponentsjs#~0.6",
|
||||
"font-roboto": "Polymer/font-roboto#~0.5.5",
|
||||
"core-header-panel": "polymer/core-header-panel#~0.5.5",
|
||||
"core-toolbar": "polymer/core-toolbar#~0.5.5",
|
||||
@ -36,9 +36,12 @@
|
||||
"paper-slider": "polymer/paper-slider#~0.5.5",
|
||||
"paper-checkbox": "polymer/paper-checkbox#~0.5.5",
|
||||
"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-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>
|
||||
<script>
|
||||
(function() {
|
||||
var timeFormatOptions = {hour: 'numeric', minute: '2-digit'};
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
|
||||
Polymer({
|
||||
time: "",
|
||||
|
||||
dateObjChanged: function(oldVal, newVal) {
|
||||
if (!newVal) {
|
||||
if (newVal) {
|
||||
this.time = uiUtil.formatTime(newVal);
|
||||
} else {
|
||||
this.time = "";
|
||||
}
|
||||
|
||||
this.time = newVal.toLocaleTimeString([], timeFormatOptions);
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<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>
|
||||
{{ relativeTime }}
|
||||
</template>
|
||||
@ -34,8 +34,15 @@
|
||||
this.updateRelative();
|
||||
},
|
||||
|
||||
datetimeObjChanged: function(oldVal, newVal) {
|
||||
this.parsedDateTime = newVal;
|
||||
|
||||
this.updateRelative();
|
||||
},
|
||||
|
||||
updateRelative: function() {
|
||||
this.relativeTime = this.parsedDateTime ? moment(this.parsedDateTime).fromNow() : "";
|
||||
this.relativeTime = this.parsedDateTime ?
|
||||
moment(this.parsedDateTime).fromNow() : "";
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -37,8 +37,8 @@
|
||||
</div>
|
||||
|
||||
<div class="time-ago">
|
||||
<core-tooltip label="{{stateObj.lastChanged}}" position="bottom">
|
||||
<relative-ha-datetime datetime="{{stateObj.lastChanged}}"></relative-ha-datetime>
|
||||
<core-tooltip label="{{stateObj.lastChangedAsDate | formatDateTime}}" position="bottom">
|
||||
<relative-ha-datetime datetimeObj="{{stateObj.lastChangedAsDate}}"></relative-ha-datetime>
|
||||
</core-tooltip>
|
||||
</div>
|
||||
|
||||
|
@ -11,7 +11,9 @@
|
||||
<div>
|
||||
<state-card-content stateObj="{{stateObj}}" style='margin-bottom: 24px;'>
|
||||
</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
|
||||
stateObj="{{stateObj}}"
|
||||
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 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 class='value'>
|
||||
{{stateObj.attributes.next_rising | HATimeStripDate}}
|
||||
{{rising | formatTime}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout justified horizontal class='data-entry' id='setting'>
|
||||
<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 class='value'>
|
||||
{{stateObj.attributes.next_setting | HATimeStripDate}}
|
||||
{{setting | formatTime}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
(function() {
|
||||
var parseDateTime = window.hass.util.parseDateTime;
|
||||
|
||||
Polymer({
|
||||
stateObjChanged: function() {
|
||||
var rising = parseDateTime(this.stateObj.attributes.next_rising);
|
||||
var setting = parseDateTime(this.stateObj.attributes.next_setting);
|
||||
rising: null,
|
||||
setting: null,
|
||||
|
||||
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);
|
||||
} else {
|
||||
this.$.sunData.appendChild(this.$.setting);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
@ -7,18 +7,6 @@
|
||||
'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
|
||||
Object.defineProperties(window.hass.stateModel.prototype, {
|
||||
// how to render the card for this state
|
||||
@ -81,3 +69,5 @@
|
||||
window.hass.uiUtil = {};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<link rel="import" href="./moment-js.html">
|
||||
|
@ -3,3 +3,22 @@
|
||||
-->
|
||||
|
||||
<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.
|
||||
"""
|
||||
|
@ -5,10 +5,11 @@ homeassistant.components.history
|
||||
Provide pre-made queries on top of the recorder component.
|
||||
"""
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from itertools import groupby
|
||||
from collections import defaultdict
|
||||
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.components.recorder as recorder
|
||||
|
||||
DOMAIN = 'history'
|
||||
@ -22,7 +23,7 @@ def last_5_states(entity_id):
|
||||
query = """
|
||||
SELECT * FROM states WHERE entity_id=? AND
|
||||
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, ))
|
||||
@ -30,7 +31,7 @@ def last_5_states(entity_id):
|
||||
|
||||
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 > ? "
|
||||
data = [start_time]
|
||||
@ -64,17 +65,17 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
||||
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. """
|
||||
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:
|
||||
return []
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
|
||||
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. """
|
||||
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
|
||||
|
||||
@ -128,7 +129,7 @@ def _api_last_5_states(handler, path_match, data):
|
||||
def _api_history_period(handler, path_match, data):
|
||||
""" Return history over a period of time. """
|
||||
# 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')
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
homeassistant.components.httpinterface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
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.const import (
|
||||
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
|
||||
DOMAIN = "isy994"
|
||||
@ -31,7 +35,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def setup(hass, config):
|
||||
"""
|
||||
Setup isy994 component.
|
||||
Setup ISY994 component.
|
||||
This will automatically import associated lights, switches, and sensors.
|
||||
"""
|
||||
try:
|
||||
@ -77,6 +81,9 @@ def setup(hass, config):
|
||||
if not ISY.connected:
|
||||
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
|
||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||
('light', DISCOVER_LIGHTS),
|
||||
@ -91,8 +98,13 @@ def setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
def stop(event):
|
||||
""" Cleanup the ISY subscription. """
|
||||
ISY.auto_update = False
|
||||
|
||||
|
||||
class ISYDeviceABC(ToggleEntity):
|
||||
""" Abstract Class for an ISY device within home assistant. """
|
||||
""" Abstract Class for an ISY device. """
|
||||
|
||||
_attrs = {}
|
||||
_onattrs = []
|
||||
@ -133,7 +145,7 @@ class ISYDeviceABC(ToggleEntity):
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
""" returns the unclean value from the controller """
|
||||
""" Returns the unclean value from the controller. """
|
||||
# pylint: disable=protected-access
|
||||
return self.node.status._val
|
||||
|
||||
@ -147,7 +159,7 @@ class ISYDeviceABC(ToggleEntity):
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this isy sensor """
|
||||
""" Returns the id of this ISY sensor. """
|
||||
# pylint: disable=protected-access
|
||||
return self.node._id
|
||||
|
||||
@ -190,7 +202,7 @@ class ISYDeviceABC(ToggleEntity):
|
||||
return self.value
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" turns the device on """
|
||||
""" Turns the device on. """
|
||||
if self.domain is not 'sensor':
|
||||
attrs = [kwargs.get(name) for name in self._onattrs]
|
||||
self.node.on(*attrs)
|
||||
@ -198,7 +210,7 @@ class ISYDeviceABC(ToggleEntity):
|
||||
_LOGGER.error('ISY cannot turn on sensors.')
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" turns the device off """
|
||||
""" Turns the device off. """
|
||||
if self.domain is not 'sensor':
|
||||
self.node.off()
|
||||
else:
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
homeassistant.keyboard
|
||||
homeassistant.components.keyboard
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
@ -1,17 +1,16 @@
|
||||
"""
|
||||
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 homeassistant import State, DOMAIN as HA_DOMAIN
|
||||
from homeassistant.const import (
|
||||
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
||||
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.sun as sun
|
||||
|
||||
@ -38,10 +37,11 @@ def setup(hass, config):
|
||||
|
||||
def _handle_get_logbook(handler, path_match, data):
|
||||
""" Return logbook entries. """
|
||||
start_today = datetime.now().date()
|
||||
start_today = dt_util.now().replace(hour=0, minute=0, second=0)
|
||||
|
||||
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):
|
||||
@ -60,7 +60,7 @@ class Entry(object):
|
||||
def as_dict(self):
|
||||
""" Convert Entry to a dict to be used within JSON. """
|
||||
return {
|
||||
'when': util.datetime_to_str(self.when),
|
||||
'when': dt_util.datetime_to_str(self.when),
|
||||
'name': self.name,
|
||||
'message': self.message,
|
||||
'domain': self.domain,
|
||||
|
@ -1,10 +1,10 @@
|
||||
"""
|
||||
homeassistant.components.media_player.chromecast
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
homeassistant.components.media_player.demo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Demo implementation of the media player.
|
||||
"""
|
||||
|
||||
"""
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
|
||||
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)
|
||||
|
||||
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:
|
||||
@ -27,10 +30,8 @@ import logging
|
||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
# The domain of your component. Should be equal to the name of your component
|
||||
DOMAIN = "modbus"
|
||||
|
||||
# List of component names (string) your component depends upon
|
||||
DEPENDENCIES = []
|
||||
|
||||
# Type of network
|
||||
@ -86,11 +87,11 @@ def setup(hass, config):
|
||||
return False
|
||||
|
||||
def stop_modbus(event):
|
||||
""" Stop Modbus service"""
|
||||
""" Stop Modbus service. """
|
||||
NETWORK.close()
|
||||
|
||||
def start_modbus(event):
|
||||
""" Start Modbus service"""
|
||||
""" Start Modbus service. """
|
||||
NETWORK.connect()
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
homeassistant.components.notify
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides functionality to notify people.
|
||||
"""
|
||||
@ -73,7 +73,7 @@ def setup(hass, config):
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class BaseNotificationService(object):
|
||||
""" Provides an ABC for notifcation services. """
|
||||
""" Provides an ABC for notification services. """
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
"""
|
||||
homeassistant.components.notify.pushover
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pushover platform for notify component.
|
||||
|
||||
Configuration:
|
||||
@ -11,7 +14,7 @@ notify:
|
||||
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
|
||||
|
||||
VARIABLES:
|
||||
Variables:
|
||||
|
||||
api_key
|
||||
*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 sqlite3
|
||||
from datetime import datetime, date
|
||||
import time
|
||||
import json
|
||||
import atexit
|
||||
|
||||
from homeassistant import Event, EventOrigin, State
|
||||
import homeassistant.util.dt as date_util
|
||||
from homeassistant.remote import JSONEncoder
|
||||
from homeassistant.const import (
|
||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||
@ -60,8 +60,9 @@ def row_to_state(row):
|
||||
""" Convert a databsae row to a state. """
|
||||
try:
|
||||
return State(
|
||||
row[1], row[2], json.loads(row[3]), datetime.fromtimestamp(row[4]),
|
||||
datetime.fromtimestamp(row[5]))
|
||||
row[1], row[2], json.loads(row[3]),
|
||||
date_util.utc_from_timestamp(row[4]),
|
||||
date_util.utc_from_timestamp(row[5]))
|
||||
except ValueError:
|
||||
# When json.loads fails
|
||||
_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. """
|
||||
try:
|
||||
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:
|
||||
# When json.loads fails
|
||||
_LOGGER.exception("Error converting row to event: %s", row)
|
||||
@ -113,10 +114,10 @@ class RecorderRun(object):
|
||||
self.start = _INSTANCE.recording_start
|
||||
self.closed_incorrect = False
|
||||
else:
|
||||
self.start = datetime.fromtimestamp(row[1])
|
||||
self.start = date_util.utc_from_timestamp(row[1])
|
||||
|
||||
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])
|
||||
|
||||
@ -166,7 +167,8 @@ class Recorder(threading.Thread):
|
||||
self.queue = queue.Queue()
|
||||
self.quit_object = object()
|
||||
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):
|
||||
""" Start recording. """
|
||||
@ -187,16 +189,21 @@ class Recorder(threading.Thread):
|
||||
if event == self.quit_object:
|
||||
self._close_run()
|
||||
self._close_connection()
|
||||
self.queue.task_done()
|
||||
return
|
||||
|
||||
elif event.event_type == EVENT_TIME_CHANGED:
|
||||
self.queue.task_done()
|
||||
continue
|
||||
|
||||
elif event.event_type == EVENT_STATE_CHANGED:
|
||||
self.record_state(
|
||||
event.data['entity_id'], event.data.get('new_state'))
|
||||
event_id = self.record_event(event)
|
||||
|
||||
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):
|
||||
""" Listens for new events on the EventBus and puts them
|
||||
@ -207,33 +214,43 @@ class Recorder(threading.Thread):
|
||||
""" Tells the recorder to shut down. """
|
||||
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. """
|
||||
now = datetime.now()
|
||||
now = date_util.utcnow()
|
||||
|
||||
# State got deleted
|
||||
if state is None:
|
||||
info = (entity_id, '', "{}", now, now, now)
|
||||
state_state = ''
|
||||
state_attr = '{}'
|
||||
last_changed = last_updated = now
|
||||
else:
|
||||
info = (
|
||||
entity_id.lower(), state.state, json.dumps(state.attributes),
|
||||
state.last_changed, state.last_updated, now)
|
||||
state_state = state.state
|
||||
state_attr = json.dumps(state.attributes)
|
||||
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(
|
||||
"INSERT INTO states ("
|
||||
"entity_id, state, attributes, last_changed, last_updated,"
|
||||
"created) VALUES (?, ?, ?, ?, ?, ?)", info)
|
||||
"created, utc_offset, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
info)
|
||||
|
||||
def record_event(self, event):
|
||||
""" Save an event to the database. """
|
||||
info = (
|
||||
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 ("
|
||||
"event_type, event_data, origin, created, time_fired"
|
||||
") VALUES (?, ?, ?, ?, ?)", info)
|
||||
"event_type, event_data, origin, created, time_fired, utc_offset"
|
||||
") VALUES (?, ?, ?, ?, ?, ?)", info, RETURN_LASTROWID)
|
||||
|
||||
def query(self, sql_query, data=None, return_value=None):
|
||||
""" Query the database. """
|
||||
@ -262,6 +279,10 @@ class Recorder(threading.Thread):
|
||||
"Error querying the database using: %s", sql_query)
|
||||
return []
|
||||
|
||||
def block_till_done(self):
|
||||
""" Blocks till all events processed. """
|
||||
self.queue.join()
|
||||
|
||||
def _setup_connection(self):
|
||||
""" Ensure database is ready to fly. """
|
||||
db_path = self.hass.config.path(DB_FILE)
|
||||
@ -282,7 +303,7 @@ class Recorder(threading.Thread):
|
||||
def save_migration(migration_id):
|
||||
""" Save and commit a migration to the database. """
|
||||
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
||||
(migration_id, datetime.now()))
|
||||
(migration_id, date_util.utcnow()))
|
||||
self.conn.commit()
|
||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||
|
||||
@ -297,7 +318,7 @@ class Recorder(threading.Thread):
|
||||
migration_id = 0
|
||||
|
||||
if migration_id < 1:
|
||||
cur.execute("""
|
||||
self.query("""
|
||||
CREATE TABLE recorder_runs (
|
||||
run_id integer primary key,
|
||||
start integer,
|
||||
@ -306,7 +327,7 @@ class Recorder(threading.Thread):
|
||||
created integer)
|
||||
""")
|
||||
|
||||
cur.execute("""
|
||||
self.query("""
|
||||
CREATE TABLE events (
|
||||
event_id integer primary key,
|
||||
event_type text,
|
||||
@ -314,10 +335,10 @@ class Recorder(threading.Thread):
|
||||
origin text,
|
||||
created integer)
|
||||
""")
|
||||
cur.execute(
|
||||
self.query(
|
||||
'CREATE INDEX events__event_type ON events(event_type)')
|
||||
|
||||
cur.execute("""
|
||||
self.query("""
|
||||
CREATE TABLE states (
|
||||
state_id integer primary key,
|
||||
entity_id text,
|
||||
@ -327,20 +348,57 @@ class Recorder(threading.Thread):
|
||||
last_updated 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)
|
||||
|
||||
if migration_id < 2:
|
||||
cur.execute("""
|
||||
self.query("""
|
||||
ALTER TABLE events
|
||||
ADD COLUMN time_fired integer
|
||||
""")
|
||||
|
||||
cur.execute('UPDATE events SET time_fired=created')
|
||||
self.query('UPDATE events SET time_fired=created')
|
||||
|
||||
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):
|
||||
""" Close connection to the database. """
|
||||
_LOGGER.info("Closing database")
|
||||
@ -356,19 +414,20 @@ class Recorder(threading.Thread):
|
||||
_LOGGER.warning("Found unfinished sessions")
|
||||
|
||||
self.query(
|
||||
"INSERT INTO recorder_runs (start, created) VALUES (?, ?)",
|
||||
(self.recording_start, datetime.now()))
|
||||
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
||||
VALUES (?, ?, ?)""",
|
||||
(self.recording_start, date_util.utcnow(), self.utc_offset))
|
||||
|
||||
def _close_run(self):
|
||||
""" Save end time for current run. """
|
||||
self.query(
|
||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||
(datetime.now(), self.recording_start))
|
||||
(date_util.utcnow(), self.recording_start))
|
||||
|
||||
|
||||
def _adapt_datetime(datetimestamp):
|
||||
""" 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():
|
||||
|
@ -1,19 +1,19 @@
|
||||
"""
|
||||
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.
|
||||
|
||||
It will read a json object from schedule.json in the config dir
|
||||
and create a schedule based on it.
|
||||
Each schedule is a JSON with the keys id, name, description,
|
||||
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
|
||||
- entity_ids an array with entity ids that the events in the schedule should
|
||||
effect (can also be groups)
|
||||
- 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 json
|
||||
@ -22,7 +22,6 @@ from homeassistant import bootstrap
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
|
||||
# The domain of your component. Should be equal to the name of your component
|
||||
DOMAIN = 'scheduler'
|
||||
|
||||
DEPENDENCIES = []
|
||||
@ -33,10 +32,10 @@ _SCHEDULE_FILE = 'schedule.json'
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Create the schedules """
|
||||
""" Create the schedules. """
|
||||
|
||||
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']
|
||||
component = event_type
|
||||
|
||||
@ -52,7 +51,7 @@ def setup(hass, config):
|
||||
event_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'],
|
||||
name=schedule_data['name'],
|
||||
@ -97,17 +96,17 @@ class Schedule(object):
|
||||
self.__event_listeners = []
|
||||
|
||||
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)
|
||||
|
||||
def schedule(self, hass):
|
||||
""" Schedule all the events in the schdule """
|
||||
""" Schedule all the events in the schedule. """
|
||||
for event in self.__event_listeners:
|
||||
event.schedule(hass)
|
||||
|
||||
|
||||
class EventListener(object):
|
||||
""" The base EventListner class that the schedule uses """
|
||||
""" The base EventListener class that the schedule uses. """
|
||||
def __init__(self, schedule):
|
||||
self.my_schedule = schedule
|
||||
|
||||
@ -122,7 +121,7 @@ class EventListener(object):
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ServiceEventListener(EventListener):
|
||||
""" A EventListner that calls a service when executed """
|
||||
""" A EventListener that calls a service when executed. """
|
||||
|
||||
def __init__(self, schdule, service):
|
||||
EventListener.__init__(self, schdule)
|
||||
@ -130,7 +129,7 @@ class ServiceEventListener(EventListener):
|
||||
(self.domain, self.service) = service.split('.')
|
||||
|
||||
def execute(self, hass):
|
||||
""" Call the service """
|
||||
""" Call the service. """
|
||||
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
||||
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
|
||||
every specified day at the time specified.
|
||||
A time event need to have the type 'time', which service to call and at
|
||||
@ -11,17 +13,17 @@ which time.
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.scheduler import ServiceEventListener
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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']
|
||||
(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
|
||||
class TimeEventListener(ServiceEventListener):
|
||||
""" The time event that the scheduler uses """
|
||||
""" The time event that the scheduler uses. """
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, schedule, service, hour, minute, second):
|
||||
@ -43,16 +45,14 @@ class TimeEventListener(ServiceEventListener):
|
||||
self.second = second
|
||||
|
||||
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,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=0)
|
||||
next_time = dt_util.now().replace(
|
||||
hour=self.hour, minute=self.minute, second=self.second)
|
||||
|
||||
# Calculate the next time the event should be executed.
|
||||
# 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 = 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.
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
import homeassistant.util.dt as date_util
|
||||
import threading
|
||||
|
||||
from homeassistant.util import split_entity_id
|
||||
@ -109,7 +110,7 @@ class Script(object):
|
||||
self._call_service(action)
|
||||
elif CONF_DELAY in action:
|
||||
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, point_in_time)
|
||||
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.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
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||
HIDDEN_STRING)
|
||||
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):
|
||||
""" Sets up the isy994 platform. """
|
||||
""" Sets up the ISY994 platform. """
|
||||
# pylint: disable=protected-access
|
||||
logger = logging.getLogger(__name__)
|
||||
devs = []
|
||||
@ -81,7 +84,7 @@ class WeatherPseudoNode(object):
|
||||
|
||||
|
||||
class ISYSensorDevice(ISYDeviceABC):
|
||||
""" represents a isy sensor within home assistant. """
|
||||
""" Represents a ISY 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Monitors SABnzbd NZB client API
|
||||
|
||||
@ -22,14 +22,12 @@ sensor:
|
||||
- type: 'disk_size'
|
||||
- type: 'disk_free'
|
||||
|
||||
VARIABLES:
|
||||
Variables:
|
||||
|
||||
base_url
|
||||
*Required
|
||||
This is the base URL of your SABnzbd instance including the port number if not
|
||||
running on 80
|
||||
Example: http://192.168.1.32:8124/
|
||||
|
||||
running on 80. Example: http://192.168.1.32:8124/
|
||||
|
||||
name
|
||||
*Optional
|
||||
@ -44,9 +42,7 @@ 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
|
||||
|
||||
|
||||
list of all available variables.
|
||||
"""
|
||||
|
||||
from homeassistant.util import Throttle
|
||||
@ -75,7 +71,7 @@ _THROTTLED_REFRESH = None
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the sensors """
|
||||
""" Sets up the sensors. """
|
||||
api_key = config.get("api_key")
|
||||
base_url = config.get("base_url")
|
||||
name = config.get("name", "SABnzbd")
|
||||
|
@ -1,9 +1,46 @@
|
||||
"""
|
||||
homeassistant.components.sensor.systemmonitor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
@ -28,7 +65,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the sensors """
|
||||
""" Sets up the sensors. """
|
||||
|
||||
dev = []
|
||||
for resource in config['resources']:
|
||||
@ -43,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class SystemMonitorSensor(Entity):
|
||||
""" A system monitor sensor """
|
||||
""" A system monitor sensor. """
|
||||
|
||||
def __init__(self, sensor_type, 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Monitors Transmission BitTorrent client API
|
||||
|
||||
@ -21,17 +21,15 @@ sensor:
|
||||
- type: 'download_speed'
|
||||
- type: 'upload_speed'
|
||||
|
||||
VARIABLES:
|
||||
Variables:
|
||||
|
||||
host
|
||||
*Required
|
||||
This is the IP address of your Transmission Daemon
|
||||
Example: 192.168.1.32
|
||||
This is the IP address of your Transmission daemon. Example: 192.168.1.32
|
||||
|
||||
port
|
||||
*Optional
|
||||
The port your Transmission daemon uses, defaults to 9091
|
||||
Example: 8080
|
||||
The port your Transmission daemon uses, defaults to 9091. Example: 8080
|
||||
|
||||
username
|
||||
*Required
|
||||
@ -43,7 +41,7 @@ Your Transmission password
|
||||
|
||||
name
|
||||
*Optional
|
||||
The name to use when displaying this Transmission instance
|
||||
The name to use when displaying this Transmission instance.
|
||||
|
||||
monitored_variables
|
||||
*Required
|
||||
@ -54,9 +52,7 @@ 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
|
||||
|
||||
|
||||
list of all available variables.
|
||||
"""
|
||||
|
||||
from homeassistant.util import Throttle
|
||||
@ -84,7 +80,7 @@ _THROTTLED_REFRESH = None
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the sensors """
|
||||
""" Sets up the sensors. """
|
||||
host = config.get(CONF_HOST)
|
||||
username = config.get(CONF_USERNAME, None)
|
||||
password = config.get(CONF_PASSWORD, None)
|
||||
@ -123,7 +119,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class TransmissionSensor(Entity):
|
||||
""" A Transmission sensor """
|
||||
""" A Transmission sensor. """
|
||||
|
||||
def __init__(self, sensor_type, transmission_client, client_name):
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
@ -158,7 +154,7 @@ class TransmissionSensor(Entity):
|
||||
)
|
||||
|
||||
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()
|
||||
if self.type == 'current_status':
|
||||
if self.transmission_client.session:
|
||||
|
@ -1,7 +1,11 @@
|
||||
"""
|
||||
homeassistant.components.sensor.vera
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for Vera sensors.
|
||||
|
||||
Configuration:
|
||||
|
||||
To use the Vera sensors you will need to add something like the following to
|
||||
your config/configuration.yaml
|
||||
|
||||
@ -15,7 +19,7 @@ sensor:
|
||||
13:
|
||||
name: Another sensor
|
||||
|
||||
VARIABLES:
|
||||
Variables:
|
||||
|
||||
vera_controller_url
|
||||
*Required
|
||||
@ -96,12 +100,12 @@ def get_devices(hass, config):
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
class VeraSensor(Entity):
|
||||
""" Represents a Vera Sensor """
|
||||
""" Represents a Vera Sensor. """
|
||||
|
||||
def __init__(self, vera_device, extra_data=None):
|
||||
self.vera_device = vera_device
|
||||
|
@ -20,7 +20,7 @@ DEPENDENCIES = ['group', 'device_tracker', 'light']
|
||||
CONF_KNOWN_LIGHT = "known_light"
|
||||
|
||||
# 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"
|
||||
|
||||
# Services to test the alarms
|
||||
|
@ -22,10 +22,16 @@ which event (sunset or sunrise) and the offset.
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
DEPENDENCIES = []
|
||||
@ -49,13 +55,21 @@ def is_on(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
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
|
||||
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):
|
||||
# AttributeError if state is None
|
||||
# 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):
|
||||
""" 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
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
|
||||
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):
|
||||
# AttributeError if state is None
|
||||
# KeyError if STATE_ATTR_NEXT_RISING does not exist
|
||||
@ -80,78 +102,94 @@ def setup(hass, config):
|
||||
""" Tracks the state of the sun. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import ephem
|
||||
except ImportError:
|
||||
if ephem is None:
|
||||
logger.exception("Error while importing dependency ephem.")
|
||||
return False
|
||||
|
||||
sun = ephem.Sun() # pylint: disable=no-member
|
||||
|
||||
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))
|
||||
if None in (hass.config.latitude, hass.config.longitude):
|
||||
logger.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
def update_sun_state(now):
|
||||
""" Method to update the current state of the sun and
|
||||
set time of next setting and rising. """
|
||||
utc_offset = datetime.utcnow() - datetime.now()
|
||||
utc_now = now + utc_offset
|
||||
try:
|
||||
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
|
||||
except ValueError:
|
||||
# Raised when invalid latitude or longitude is given to Observer
|
||||
logger.exception("Invalid value for latitude or longitude")
|
||||
return False
|
||||
|
||||
observer = ephem.Observer()
|
||||
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())
|
||||
sun.point_in_time_listener(dt_util.utcnow())
|
||||
|
||||
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):
|
||||
""" Create a sun event listener based on the description. """
|
||||
|
||||
@ -195,7 +233,7 @@ class SunEventListener(ServiceEventListener):
|
||||
else:
|
||||
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 = 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.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
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||
HIDDEN_STRING)
|
||||
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):
|
||||
""" Sets up the isy994 platform. """
|
||||
""" Sets up the ISY994 platform. """
|
||||
# pylint: disable=too-many-locals
|
||||
logger = logging.getLogger(__name__)
|
||||
devs = []
|
||||
@ -54,7 +57,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class ISYSwitchDevice(ISYDeviceABC):
|
||||
""" represents as isy light within home assistant. """
|
||||
""" Represents as ISY light. """
|
||||
|
||||
_domain = 'switch'
|
||||
_dtype = 'binary'
|
||||
@ -62,7 +65,7 @@ class ISYSwitchDevice(ISYDeviceABC):
|
||||
|
||||
|
||||
class ISYProgramDevice(ISYSwitchDevice):
|
||||
""" represents a door that can be manipulated within home assistant. """
|
||||
""" Represents a door that can be manipulated. """
|
||||
|
||||
_domain = 'switch'
|
||||
_dtype = 'binary'
|
||||
@ -74,9 +77,9 @@ class ISYProgramDevice(ISYSwitchDevice):
|
||||
self.action_node = actions
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" turns the device on/closes the device """
|
||||
""" Turns the device on/closes the device. """
|
||||
self.action_node.runThen()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" turns the device off/opens the device """
|
||||
""" Turns the device off/opens the device. """
|
||||
self.action_node.runElse()
|
||||
|
@ -1,4 +1,7 @@
|
||||
"""
|
||||
homeassistant.components.switch.modbus
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for Modbus switches.
|
||||
|
||||
Configuration:
|
||||
@ -53,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class ModbusSwitch(ToggleEntity):
|
||||
""" Represents a Modbus Switch """
|
||||
""" Represents a Modbus switch. """
|
||||
|
||||
def __init__(self, name, slave, register, bit):
|
||||
self._name = name
|
||||
|
@ -1,4 +1,9 @@
|
||||
""" Support for Tellstick switches. """
|
||||
"""
|
||||
homeassistant.components.switch.tellstick
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for Tellstick switches.
|
||||
"""
|
||||
import logging
|
||||
|
||||
|
||||
@ -9,7 +14,7 @@ import tellcore.constants as tellcore_constants
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Find and return tellstick switches. """
|
||||
""" Find and return Tellstick switches. """
|
||||
try:
|
||||
import tellcore.telldus as telldus
|
||||
except ImportError:
|
||||
@ -30,7 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
|
||||
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 |
|
||||
tellcore_constants.TELLSTICK_TURNOFF)
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
""" Support for WeMo switchces. """
|
||||
"""
|
||||
homeassistant.components.switch.wemo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for WeMo switches.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
@ -8,7 +13,7 @@ from homeassistant.components.switch import (
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Find and return wemo switches. """
|
||||
""" Find and return WeMo switches. """
|
||||
try:
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
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):
|
||||
""" represents a WeMo switch within home assistant. """
|
||||
""" Represents a WeMo switch within Home Assistant. """
|
||||
def __init__(self, wemo):
|
||||
self.wemo = wemo
|
||||
|
||||
@ -78,5 +83,5 @@ class WemoSwitch(ToggleEntity):
|
||||
self.wemo.off()
|
||||
|
||||
def update(self):
|
||||
""" Update Wemo state. """
|
||||
""" Update WeMo state. """
|
||||
self.wemo.get_state(True)
|
||||
|
@ -1,4 +1,9 @@
|
||||
""" Support for WeMo switchces. """
|
||||
"""
|
||||
homeassistant.components.switch.wink
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for Wink switches.
|
||||
"""
|
||||
import logging
|
||||
|
||||
# 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.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.
|
||||
"""
|
||||
import logging
|
||||
@ -53,14 +56,14 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class WinkToggleDevice(ToggleEntity):
|
||||
""" represents a Wink switch within home assistant. """
|
||||
""" Represents a Wink switch within Home Assistant. """
|
||||
|
||||
def __init__(self, wink):
|
||||
self.wink = wink
|
||||
|
||||
@property
|
||||
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())
|
||||
|
||||
@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_NAME = "name"
|
||||
CONF_TIME_ZONE = "time_zone"
|
||||
CONF_VISIBILITY = "visibility"
|
||||
CONF_CUSTOMIZE = "customize"
|
||||
|
||||
CONF_PLATFORM = "platform"
|
||||
CONF_HOST = "host"
|
||||
@ -111,6 +111,7 @@ SERVER_PORT = 8123
|
||||
URL_ROOT = "/"
|
||||
URL_API = "/api/"
|
||||
URL_API_STREAM = "/api/stream"
|
||||
URL_API_CONFIG = "/api/config"
|
||||
URL_API_STATES = "/api/states"
|
||||
URL_API_STATES_ENTITY = "/api/states/{}"
|
||||
URL_API_EVENTS = "/api/events"
|
||||
@ -119,6 +120,7 @@ URL_API_SERVICES = "/api/services"
|
||||
URL_API_SERVICES_SERVICE = "/api/services/{}/{}"
|
||||
URL_API_EVENT_FORWARD = "/api/event_forwarding"
|
||||
URL_API_COMPONENTS = "/api/components"
|
||||
URL_API_BOOTSTRAP = "/api/bootstrap"
|
||||
|
||||
HTTP_OK = 200
|
||||
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.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from homeassistant import NoEntitySpecifiedError
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON,
|
||||
STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN,
|
||||
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
|
||||
# Dict mapping entity_id to a boolean that overwrites the hidden property
|
||||
_OVERWRITE_HIDDEN = {}
|
||||
_OVERWRITE = defaultdict(dict)
|
||||
|
||||
|
||||
class Entity(object):
|
||||
@ -121,8 +124,15 @@ class Entity(object):
|
||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
||||
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
||||
|
||||
if _OVERWRITE_HIDDEN.get(self.entity_id, self.hidden):
|
||||
attr[ATTR_HIDDEN] = True
|
||||
if self.hidden:
|
||||
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
|
||||
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
||||
@ -143,15 +153,18 @@ class Entity(object):
|
||||
return "<Entity {}: {}>".format(self.name, self.state)
|
||||
|
||||
@staticmethod
|
||||
def overwrite_hidden(entity_id, hidden):
|
||||
def overwrite_attribute(entity_id, attrs, vals):
|
||||
"""
|
||||
Overwrite the hidden property of an entity.
|
||||
Set hidden to None to remove any overwritten value in place.
|
||||
Overwrite any attribute of an entity.
|
||||
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:
|
||||
_OVERWRITE_HIDDEN.pop(entity_id, None)
|
||||
else:
|
||||
_OVERWRITE_HIDDEN[entity_id.lower()] = hidden
|
||||
for attr, val in zip(attrs, vals):
|
||||
if val is None:
|
||||
_OVERWRITE[entity_id.lower()].pop(attr, None)
|
||||
else:
|
||||
_OVERWRITE[entity_id.lower()][attr] = val
|
||||
|
||||
|
||||
class ToggleEntity(Entity):
|
||||
|
@ -4,7 +4,7 @@ homeassistant.helpers.entity_component
|
||||
|
||||
Provides helpers for components that manage entities.
|
||||
"""
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.bootstrap import prepare_setup_platform
|
||||
from homeassistant.helpers import (
|
||||
generate_entity_id, config_per_platform, extract_entity_ids)
|
||||
from homeassistant.components import group, discovery
|
||||
@ -35,12 +35,16 @@ class EntityComponent(object):
|
||||
self.group = None
|
||||
self.is_polling = False
|
||||
|
||||
self.config = None
|
||||
|
||||
def setup(self, config):
|
||||
"""
|
||||
Sets up a full entity component:
|
||||
- Loads the platforms from the config
|
||||
- Will listen for supported discovered platforms
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||
for p_type, p_config in \
|
||||
config_per_platform(config, self.domain, self.logger):
|
||||
@ -115,18 +119,20 @@ class EntityComponent(object):
|
||||
self._update_entity_states,
|
||||
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. """
|
||||
platform_name = '{}.{}'.format(self.domain, platform_type)
|
||||
platform = get_component(platform_name)
|
||||
platform = prepare_setup_platform(
|
||||
self.hass, self.config, self.domain, platform_type)
|
||||
|
||||
if platform is None:
|
||||
self.logger.error('Unable to find platform %s', platform_type)
|
||||
return
|
||||
|
||||
platform_name = '{}.{}'.format(self.domain, platform_type)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
@ -135,15 +141,16 @@ class EntityComponent(object):
|
||||
# Support old deprecated method for now - 3/1/2015
|
||||
if hasattr(platform, 'get_devices'):
|
||||
self.logger.warning(
|
||||
"Please upgrade %s to return new entities using "
|
||||
"setup_platform. See %s/demo.py for an example.",
|
||||
'Please upgrade %s to return new entities using '
|
||||
'setup_platform. See %s/demo.py for an example.',
|
||||
platform_name, self.domain)
|
||||
self.add_entities(platform.get_devices(self.hass, config))
|
||||
self.add_entities(
|
||||
platform.get_devices(self.hass, platform_config))
|
||||
|
||||
else:
|
||||
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
|
||||
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.
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant import State
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
|
||||
@ -26,7 +26,7 @@ class TrackStates(object):
|
||||
self.states = []
|
||||
|
||||
def __enter__(self):
|
||||
self.now = datetime.now()
|
||||
self.now = dt_util.utcnow()
|
||||
return self.states
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
|
@ -8,7 +8,7 @@ import collections
|
||||
from itertools import chain
|
||||
import threading
|
||||
import queue
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
import re
|
||||
import enum
|
||||
import socket
|
||||
@ -16,12 +16,19 @@ import random
|
||||
import string
|
||||
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_PATH = re.compile(r'(~|\.(\.)+)')
|
||||
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
||||
|
||||
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
|
||||
|
||||
|
||||
def sanitize_filename(filename):
|
||||
""" Sanitizes a filename by removing .. / and \\. """
|
||||
@ -40,33 +47,6 @@ def slugify(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):
|
||||
""" Splits a state entity_id into domain, object_id. """
|
||||
return entity_id.split(".", 1)
|
||||
@ -79,7 +59,7 @@ def repr_helper(inp):
|
||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||
in inp.items())
|
||||
elif isinstance(inp, datetime):
|
||||
return datetime_to_str(inp)
|
||||
return datetime_to_local_str(inp)
|
||||
else:
|
||||
return str(inp)
|
||||
|
||||
@ -174,6 +154,32 @@ def get_random_string(length=10):
|
||||
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):
|
||||
""" Taken from Python 3.4.0 docs. """
|
||||
# pylint: disable=no-init, too-few-public-methods
|
||||
@ -436,7 +442,7 @@ class ThreadPool(object):
|
||||
return
|
||||
|
||||
# Add to current running jobs
|
||||
job_log = (datetime.now(), job)
|
||||
job_log = (utcnow(), job)
|
||||
self.current_jobs.append(job_log)
|
||||
|
||||
# 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
|
||||
requests>=2.0
|
||||
pyyaml>=3.11
|
||||
pytz>=2015.2
|
||||
|
||||
# optional, needed for specific components
|
||||
|
||||
@ -40,8 +41,15 @@ PyISY>=1.0.2
|
||||
# sensor.systemmonitor
|
||||
psutil>=2.2.1
|
||||
|
||||
#pushover notifications
|
||||
# pushover notifications
|
||||
python-pushover>=0.2
|
||||
|
||||
# Transmission Torrent Client
|
||||
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 ..
|
||||
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. """
|
||||
self.devices_home.remove(device)
|
||||
|
||||
def reset(self):
|
||||
""" Resets which devices are home. """
|
||||
self.devices_home = []
|
||||
|
||||
def scan_devices(self):
|
||||
""" Returns a list of fake devices. """
|
||||
|
||||
|
@ -5,16 +5,36 @@ tests.helper
|
||||
Helper method for writing tests.
|
||||
"""
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
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. """
|
||||
if num_threads:
|
||||
orig_num_threads = ha.MIN_WORKER_THREAD
|
||||
ha.MIN_WORKER_THREAD = num_threads
|
||||
|
||||
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
|
||||
|
||||
@ -32,6 +52,44 @@ def mock_service(hass, domain, service):
|
||||
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):
|
||||
""" Provides a fake module. """
|
||||
|
||||
|
@ -4,13 +4,10 @@ tests.test_component_demo
|
||||
|
||||
Tests demo component.
|
||||
"""
|
||||
# pylint: disable=too-many-public-methods,protected-access
|
||||
import unittest
|
||||
|
||||
import homeassistant as ha
|
||||
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):
|
||||
@ -23,46 +20,6 @@ class TestDemo(unittest.TestCase):
|
||||
""" Stop down stuff we started. """
|
||||
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):
|
||||
""" Test if demo state shows if we give no configuration. """
|
||||
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
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import os
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
|
||||
import homeassistant.components.device_tracker as device_tracker
|
||||
@ -80,6 +81,8 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
scanner = loader.get_component(
|
||||
'device_tracker.test').get_scanner(None, None)
|
||||
|
||||
scanner.reset()
|
||||
|
||||
scanner.come_home('DEV1')
|
||||
scanner.come_home('DEV2')
|
||||
|
||||
@ -116,7 +119,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2')
|
||||
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
|
||||
# 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
|
||||
import unittest
|
||||
import datetime as dt
|
||||
from datetime import timedelta
|
||||
|
||||
import ephem
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
import homeassistant.components.sun as sun
|
||||
|
||||
|
||||
@ -42,22 +43,20 @@ class TestSun(unittest.TestCase):
|
||||
observer.lat = '32.87336' # 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
|
||||
next_rising_dt = ephem.localtime(
|
||||
observer.next_rising(body_sun, start=utc_now))
|
||||
next_setting_dt = ephem.localtime(
|
||||
observer.next_setting(body_sun, start=utc_now))
|
||||
next_rising_dt = observer.next_rising(
|
||||
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||
next_setting_dt = observer.next_setting(
|
||||
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||
|
||||
# Home Assistant strips out microseconds
|
||||
# strip it out of the datetime objects
|
||||
next_rising_dt = next_rising_dt - dt.timedelta(
|
||||
microseconds=next_rising_dt.microsecond)
|
||||
next_setting_dt = next_setting_dt - dt.timedelta(
|
||||
microseconds=next_setting_dt.microsecond)
|
||||
next_rising_dt = dt_util.strip_microseconds(next_rising_dt)
|
||||
next_setting_dt = dt_util.strip_microseconds(next_setting_dt)
|
||||
|
||||
self.assertEqual(next_rising_dt, sun.next_rising(self.hass))
|
||||
self.assertEqual(next_setting_dt, sun.next_setting(self.hass))
|
||||
self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass))
|
||||
self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass))
|
||||
|
||||
# Point it at a state without the proper attributes
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
||||
@ -84,7 +83,7 @@ class TestSun(unittest.TestCase):
|
||||
self.assertIsNotNone(test_time)
|
||||
|
||||
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()
|
||||
|
||||
|
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
|
||||
""" 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):
|
||||
""" Test get_config_path method. """
|
||||
@ -72,7 +76,7 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
|
||||
runs = []
|
||||
|
||||
self.hass.track_point_in_time(
|
||||
self.hass.track_point_in_utc_time(
|
||||
lambda x: runs.append(1), birthday_paulus)
|
||||
|
||||
self._send_time_changed(before_birthday)
|
||||
@ -88,7 +92,7 @@ class TestHomeAssistant(unittest.TestCase):
|
||||
self.hass.pool.block_till_done()
|
||||
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)
|
||||
|
||||
self._send_time_changed(after_birthday)
|
||||
|
@ -25,7 +25,8 @@ class TestHelpersEntity(unittest.TestCase):
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
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):
|
||||
""" 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):
|
||||
""" 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()
|
||||
|
||||
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):
|
||||
""" 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.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)"))
|
||||
|
||||
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):
|
||||
""" Test split_entity_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