mirror of https://github.com/home-assistant/core
Merge branch 'dev' into mysensors-component-switch
Conflicts: homeassistant/components/sensor/__init__.py homeassistant/components/switch/__init__.py
This commit is contained in:
commit
6ff24ed047
|
@ -15,6 +15,10 @@ omit =
|
|||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/*/tellstick.py
|
||||
|
||||
homeassistant/components/tellduslive.py
|
||||
homeassistant/components/*/tellduslive.py
|
||||
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
|
|
|
@ -41,6 +41,7 @@ Icon
|
|||
dist
|
||||
build
|
||||
eggs
|
||||
.eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
|
|
|
@ -8,8 +8,8 @@ python:
|
|||
- 3.4
|
||||
- 3.5
|
||||
install:
|
||||
# Validate requirements_all.txt on Python 3.5
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then python3 setup.py develop; script/gen_requirements_all.py validate; fi
|
||||
# Validate requirements_all.txt on Python 3.4
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python3 setup.py -q develop 2>/dev/null; tput setaf 1; script/gen_requirements_all.py validate; tput sgr0; fi
|
||||
- script/bootstrap_server
|
||||
script:
|
||||
- script/cibuild
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Paulus Schoutsen
|
||||
Copyright (c) 2016 Paulus Schoutsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
|
@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
alarms.extend([
|
||||
VerisureAlarm(value)
|
||||
for value in verisure.get_alarm_status().values()
|
||||
for value in verisure.ALARM_STATUS.values()
|
||||
if verisure.SHOW_ALARM
|
||||
])
|
||||
|
||||
|
@ -42,7 +42,6 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
|||
|
||||
def __init__(self, alarm_status):
|
||||
self._id = alarm_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
|
@ -62,36 +61,36 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
|||
|
||||
def update(self):
|
||||
""" Update alarm status """
|
||||
verisure.update()
|
||||
verisure.update_alarm()
|
||||
|
||||
if verisure.STATUS[self._device][self._id].status == 'unarmed':
|
||||
if verisure.ALARM_STATUS[self._id].status == 'unarmed':
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
|
||||
elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
|
||||
elif verisure.ALARM_STATUS[self._id].status == 'armedaway':
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif verisure.STATUS[self._device][self._id].status != 'pending':
|
||||
elif verisure.ALARM_STATUS[self._id].status != 'pending':
|
||||
_LOGGER.error(
|
||||
'Unknown alarm state %s',
|
||||
verisure.STATUS[self._device][self._id].status)
|
||||
verisure.ALARM_STATUS[self._id].status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_DISARMED)
|
||||
_LOGGER.warning('disarming')
|
||||
verisure.MY_PAGES.alarm.set(code, 'DISARMED')
|
||||
_LOGGER.info('verisure alarm disarming')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_ARMED_HOME)
|
||||
_LOGGER.warning('arming home')
|
||||
verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME')
|
||||
_LOGGER.info('verisure alarm arming home')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_ARMED_AWAY)
|
||||
_LOGGER.warning('arming away')
|
||||
verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY')
|
||||
_LOGGER.info('verisure alarm arming away')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
|
|
@ -17,6 +17,10 @@ DEPENDENCIES = ['sun']
|
|||
|
||||
CONF_OFFSET = 'offset'
|
||||
CONF_EVENT = 'event'
|
||||
CONF_BEFORE = "before"
|
||||
CONF_BEFORE_OFFSET = "before_offset"
|
||||
CONF_AFTER = "after"
|
||||
CONF_AFTER_OFFSET = "after_offset"
|
||||
|
||||
EVENT_SUNSET = 'sunset'
|
||||
EVENT_SUNRISE = 'sunrise'
|
||||
|
@ -37,26 +41,9 @@ def trigger(hass, config, action):
|
|||
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
|
||||
return False
|
||||
|
||||
if CONF_OFFSET in config:
|
||||
raw_offset = config.get(CONF_OFFSET)
|
||||
|
||||
negative_offset = False
|
||||
if raw_offset.startswith('-'):
|
||||
negative_offset = True
|
||||
raw_offset = raw_offset[1:]
|
||||
|
||||
try:
|
||||
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
|
||||
except ValueError:
|
||||
_LOGGER.error('Could not parse offset %s', raw_offset)
|
||||
return False
|
||||
|
||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||
|
||||
if negative_offset:
|
||||
offset *= -1
|
||||
else:
|
||||
offset = timedelta(0)
|
||||
offset = _parse_offset(config.get(CONF_OFFSET))
|
||||
if offset is False:
|
||||
return False
|
||||
|
||||
# Do something to call action
|
||||
if event == EVENT_SUNRISE:
|
||||
|
@ -67,6 +54,65 @@ def trigger(hass, config, action):
|
|||
return True
|
||||
|
||||
|
||||
def if_action(hass, config):
|
||||
""" Wraps action method with sun based condition. """
|
||||
before = config.get(CONF_BEFORE)
|
||||
after = config.get(CONF_AFTER)
|
||||
|
||||
# Make sure required configuration keys are present
|
||||
if before is None and after is None:
|
||||
logging.getLogger(__name__).error(
|
||||
"Missing if-condition configuration key %s or %s",
|
||||
CONF_BEFORE, CONF_AFTER)
|
||||
return None
|
||||
|
||||
# Make sure configuration keys have the right value
|
||||
if before not in (None, EVENT_SUNRISE, EVENT_SUNSET) or \
|
||||
after not in (None, EVENT_SUNRISE, EVENT_SUNSET):
|
||||
logging.getLogger(__name__).error(
|
||||
"%s and %s can only be set to %s or %s",
|
||||
CONF_BEFORE, CONF_AFTER, EVENT_SUNRISE, EVENT_SUNSET)
|
||||
return None
|
||||
|
||||
before_offset = _parse_offset(config.get(CONF_BEFORE_OFFSET))
|
||||
after_offset = _parse_offset(config.get(CONF_AFTER_OFFSET))
|
||||
if before_offset is False or after_offset is False:
|
||||
return None
|
||||
|
||||
if before is None:
|
||||
before_func = lambda: None
|
||||
elif before == EVENT_SUNRISE:
|
||||
before_func = lambda: sun.next_rising_utc(hass) + before_offset
|
||||
else:
|
||||
before_func = lambda: sun.next_setting_utc(hass) + before_offset
|
||||
|
||||
if after is None:
|
||||
after_func = lambda: None
|
||||
elif after == EVENT_SUNRISE:
|
||||
after_func = lambda: sun.next_rising_utc(hass) + after_offset
|
||||
else:
|
||||
after_func = lambda: sun.next_setting_utc(hass) + after_offset
|
||||
|
||||
def time_if():
|
||||
""" Validate time based if-condition """
|
||||
|
||||
now = dt_util.utcnow()
|
||||
before = before_func()
|
||||
after = after_func()
|
||||
|
||||
if before is not None and now > now.replace(hour=before.hour,
|
||||
minute=before.minute):
|
||||
return False
|
||||
|
||||
if after is not None and now < now.replace(hour=after.hour,
|
||||
minute=after.minute):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return time_if
|
||||
|
||||
|
||||
def trigger_sunrise(hass, action, offset):
|
||||
""" Trigger action at next sun rise. """
|
||||
def next_rise():
|
||||
|
@ -103,3 +149,26 @@ def trigger_sunset(hass, action, offset):
|
|||
action()
|
||||
|
||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||
|
||||
|
||||
def _parse_offset(raw_offset):
|
||||
if raw_offset is None:
|
||||
return timedelta(0)
|
||||
|
||||
negative_offset = False
|
||||
if raw_offset.startswith('-'):
|
||||
negative_offset = True
|
||||
raw_offset = raw_offset[1:]
|
||||
|
||||
try:
|
||||
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
|
||||
except ValueError:
|
||||
_LOGGER.error('Could not parse offset %s', raw_offset)
|
||||
return False
|
||||
|
||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||
|
||||
if negative_offset:
|
||||
offset *= -1
|
||||
|
||||
return offset
|
||||
|
|
|
@ -50,6 +50,7 @@ FLASH_LONG = "long"
|
|||
# Apply an effect to the light, can be EFFECT_COLORLOOP
|
||||
ATTR_EFFECT = "effect"
|
||||
EFFECT_COLORLOOP = "colorloop"
|
||||
EFFECT_RANDOM = "random"
|
||||
EFFECT_WHITE = "white"
|
||||
|
||||
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||
|
@ -228,7 +229,8 @@ def setup(hass, config):
|
|||
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
|
||||
params[ATTR_FLASH] = dat[ATTR_FLASH]
|
||||
|
||||
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE):
|
||||
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE,
|
||||
EFFECT_RANDOM):
|
||||
params[ATTR_EFFECT] = dat[ATTR_EFFECT]
|
||||
|
||||
for light in target_lights:
|
||||
|
|
|
@ -10,6 +10,7 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import socket
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
@ -20,7 +21,7 @@ from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME
|
|||
from homeassistant.components.light import (
|
||||
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
|
||||
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
|
||||
ATTR_EFFECT, EFFECT_COLORLOOP, ATTR_RGB_COLOR)
|
||||
ATTR_EFFECT, EFFECT_COLORLOOP, EFFECT_RANDOM, ATTR_RGB_COLOR)
|
||||
|
||||
REQUIREMENTS = ['phue==0.8']
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
@ -233,6 +234,9 @@ class HueLight(Light):
|
|||
|
||||
if effect == EFFECT_COLORLOOP:
|
||||
command['effect'] = 'colorloop'
|
||||
elif effect == EFFECT_RANDOM:
|
||||
command['hue'] = random.randrange(0, 65535)
|
||||
command['sat'] = random.randrange(150, 254)
|
||||
else:
|
||||
command['effect'] = 'none'
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ turn_on:
|
|||
description: Light effect
|
||||
values:
|
||||
- colorloop
|
||||
- random
|
||||
|
||||
turn_off:
|
||||
description: Turn a light off
|
||||
|
|
|
@ -30,7 +30,7 @@ DEFAULT_QOS = 0
|
|||
DEFAULT_RETAIN = False
|
||||
|
||||
SERVICE_PUBLISH = 'publish'
|
||||
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
||||
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
|
||||
|
||||
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import json
|
|||
import atexit
|
||||
|
||||
from homeassistant.core import Event, EventOrigin, State
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.remote import JSONEncoder
|
||||
from homeassistant.const import (
|
||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||
|
@ -62,8 +62,8 @@ def row_to_state(row):
|
|||
try:
|
||||
return State(
|
||||
row[1], row[2], json.loads(row[3]),
|
||||
date_util.utc_from_timestamp(row[4]),
|
||||
date_util.utc_from_timestamp(row[5]))
|
||||
dt_util.utc_from_timestamp(row[4]),
|
||||
dt_util.utc_from_timestamp(row[5]))
|
||||
except ValueError:
|
||||
# When json.loads fails
|
||||
_LOGGER.exception("Error converting row to state: %s", row)
|
||||
|
@ -74,7 +74,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]),
|
||||
date_util.utc_from_timestamp(row[5]))
|
||||
dt_util.utc_from_timestamp(row[5]))
|
||||
except ValueError:
|
||||
# When json.loads fails
|
||||
_LOGGER.exception("Error converting row to event: %s", row)
|
||||
|
@ -116,10 +116,10 @@ class RecorderRun(object):
|
|||
self.start = _INSTANCE.recording_start
|
||||
self.closed_incorrect = False
|
||||
else:
|
||||
self.start = date_util.utc_from_timestamp(row[1])
|
||||
self.start = dt_util.utc_from_timestamp(row[1])
|
||||
|
||||
if row[2] is not None:
|
||||
self.end = date_util.utc_from_timestamp(row[2])
|
||||
self.end = dt_util.utc_from_timestamp(row[2])
|
||||
|
||||
self.closed_incorrect = bool(row[3])
|
||||
|
||||
|
@ -169,8 +169,8 @@ class Recorder(threading.Thread):
|
|||
self.queue = queue.Queue()
|
||||
self.quit_object = object()
|
||||
self.lock = threading.Lock()
|
||||
self.recording_start = date_util.utcnow()
|
||||
self.utc_offset = date_util.now().utcoffset().total_seconds()
|
||||
self.recording_start = dt_util.utcnow()
|
||||
self.utc_offset = dt_util.now().utcoffset().total_seconds()
|
||||
|
||||
def start_recording(event):
|
||||
""" Start recording. """
|
||||
|
@ -217,10 +217,11 @@ class Recorder(threading.Thread):
|
|||
def shutdown(self, event):
|
||||
""" Tells the recorder to shut down. """
|
||||
self.queue.put(self.quit_object)
|
||||
self.block_till_done()
|
||||
|
||||
def record_state(self, entity_id, state, event_id):
|
||||
""" Save a state to the database. """
|
||||
now = date_util.utcnow()
|
||||
now = dt_util.utcnow()
|
||||
|
||||
# State got deleted
|
||||
if state is None:
|
||||
|
@ -247,7 +248,7 @@ class Recorder(threading.Thread):
|
|||
""" Save an event to the database. """
|
||||
info = (
|
||||
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
||||
str(event.origin), date_util.utcnow(), event.time_fired,
|
||||
str(event.origin), dt_util.utcnow(), event.time_fired,
|
||||
self.utc_offset
|
||||
)
|
||||
|
||||
|
@ -307,7 +308,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, date_util.utcnow()))
|
||||
(migration_id, dt_util.utcnow()))
|
||||
self.conn.commit()
|
||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||
|
||||
|
@ -420,18 +421,18 @@ class Recorder(threading.Thread):
|
|||
self.query(
|
||||
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
||||
VALUES (?, ?, ?)""",
|
||||
(self.recording_start, date_util.utcnow(), self.utc_offset))
|
||||
(self.recording_start, dt_util.utcnow(), self.utc_offset))
|
||||
|
||||
def _close_run(self):
|
||||
""" Save end time for current run. """
|
||||
self.query(
|
||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||
(date_util.utcnow(), self.recording_start))
|
||||
(dt_util.utcnow(), self.recording_start))
|
||||
|
||||
|
||||
def _adapt_datetime(datetimestamp):
|
||||
""" Turn a datetime into an integer for in the DB. """
|
||||
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||
return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||
|
||||
|
||||
def _verify_instance():
|
||||
|
|
|
@ -73,8 +73,9 @@ def _process_config(scene_config):
|
|||
|
||||
for entity_id in c_entities:
|
||||
if isinstance(c_entities[entity_id], dict):
|
||||
state = c_entities[entity_id].pop('state', None)
|
||||
attributes = c_entities[entity_id]
|
||||
entity_attrs = c_entities[entity_id].copy()
|
||||
state = entity_attrs.pop('state', None)
|
||||
attributes = entity_attrs
|
||||
else:
|
||||
state = c_entities[entity_id]
|
||||
attributes = {}
|
||||
|
|
|
@ -10,7 +10,7 @@ import logging
|
|||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.components import (
|
||||
wink, zwave, isy994, verisure, ecobee, mysensors)
|
||||
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors)
|
||||
|
||||
DOMAIN = 'sensor'
|
||||
SCAN_INTERVAL = 30
|
||||
|
@ -24,6 +24,7 @@ DISCOVERY_PLATFORMS = {
|
|||
isy994.DISCOVER_SENSORS: 'isy994',
|
||||
verisure.DISCOVER_SENSORS: 'verisure',
|
||||
ecobee.DISCOVER_SENSORS: 'ecobee',
|
||||
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
||||
mysensors.DISCOVER_SENSORS: 'mysensors',
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,11 @@ class EliqSensor(Entity):
|
|||
""" Returns the name. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Returns icon. """
|
||||
return "mdi:speedometer"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
|
|
|
@ -102,13 +102,13 @@ class RestSensor(Entity):
|
|||
self.rest.update()
|
||||
value = self.rest.data
|
||||
|
||||
if 'error' in value:
|
||||
self._state = value['error']
|
||||
else:
|
||||
if self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, STATE_UNKNOWN)
|
||||
self._state = value
|
||||
if value is None:
|
||||
value = STATE_UNKNOWN
|
||||
elif self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, STATE_UNKNOWN)
|
||||
|
||||
self._state = value
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
@ -118,7 +118,7 @@ class RestDataGet(object):
|
|||
def __init__(self, resource, verify_ssl):
|
||||
self._resource = resource
|
||||
self._verify_ssl = verify_ssl
|
||||
self.data = dict()
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
|
@ -126,12 +126,10 @@ class RestDataGet(object):
|
|||
try:
|
||||
response = requests.get(self._resource, timeout=10,
|
||||
verify=self._verify_ssl)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.text
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
||||
self.data['error'] = STATE_UNKNOWN
|
||||
self.data = None
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
@ -142,7 +140,7 @@ class RestDataPost(object):
|
|||
self._resource = resource
|
||||
self._payload = payload
|
||||
self._verify_ssl = verify_ssl
|
||||
self.data = dict()
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
|
@ -150,9 +148,7 @@ class RestDataPost(object):
|
|||
try:
|
||||
response = requests.post(self._resource, data=self._payload,
|
||||
timeout=10, verify=self._verify_ssl)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.text
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
||||
self.data['error'] = STATE_UNKNOWN
|
||||
self.data = None
|
||||
|
|
|
@ -14,25 +14,25 @@ from homeassistant.const import STATE_ON, STATE_OFF
|
|||
|
||||
REQUIREMENTS = ['psutil==3.2.2']
|
||||
SENSOR_TYPES = {
|
||||
'disk_use_percent': ['Disk Use', '%'],
|
||||
'disk_use': ['Disk Use', 'GiB'],
|
||||
'disk_free': ['Disk Free', 'GiB'],
|
||||
'memory_use_percent': ['RAM Use', '%'],
|
||||
'memory_use': ['RAM Use', 'MiB'],
|
||||
'memory_free': ['RAM Free', 'MiB'],
|
||||
'processor_use': ['CPU Use', '%'],
|
||||
'process': ['Process', ''],
|
||||
'swap_use_percent': ['Swap Use', '%'],
|
||||
'swap_use': ['Swap Use', 'GiB'],
|
||||
'swap_free': ['Swap Free', 'GiB'],
|
||||
'network_out': ['Sent', 'MiB'],
|
||||
'network_in': ['Recieved', 'MiB'],
|
||||
'packets_out': ['Packets sent', ''],
|
||||
'packets_in': ['Packets recieved', ''],
|
||||
'ipv4_address': ['IPv4 address', ''],
|
||||
'ipv6_address': ['IPv6 address', ''],
|
||||
'last_boot': ['Last Boot', ''],
|
||||
'since_last_boot': ['Since Last Boot', '']
|
||||
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||
'disk_free': ['Disk Free', 'GiB', 'mdi:harddisk'],
|
||||
'memory_use_percent': ['RAM Use', '%', 'mdi:memory'],
|
||||
'memory_use': ['RAM Use', 'MiB', 'mdi:memory'],
|
||||
'memory_free': ['RAM Free', 'MiB', 'mdi:memory'],
|
||||
'processor_use': ['CPU Use', '%', 'mdi:memory'],
|
||||
'process': ['Process', '', 'mdi:memory'],
|
||||
'swap_use_percent': ['Swap Use', '%', 'mdi:harddisk'],
|
||||
'swap_use': ['Swap Use', 'GiB', 'mdi:harddisk'],
|
||||
'swap_free': ['Swap Free', 'GiB', 'mdi:harddisk'],
|
||||
'network_out': ['Sent', 'MiB', 'mdi:server-network'],
|
||||
'network_in': ['Recieved', 'MiB', 'mdi:server-network'],
|
||||
'packets_out': ['Packets sent', '', 'mdi:server-network'],
|
||||
'packets_in': ['Packets recieved', '', 'mdi:server-network'],
|
||||
'ipv4_address': ['IPv4 address', '', 'mdi:server-network'],
|
||||
'ipv6_address': ['IPv6 address', '', 'mdi:server-network'],
|
||||
'last_boot': ['Last Boot', '', 'mdi:clock'],
|
||||
'since_last_boot': ['Since Last Boot', '', 'mdi:clock']
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -69,6 +69,10 @@ class SystemMonitorSensor(Entity):
|
|||
def name(self):
|
||||
return self._name.rstrip()
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return SENSOR_TYPES[self.type][2]
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
homeassistant.components.sensor.tellduslive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Shows sensor values from Tellstick Net/Telstick Live.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.tellduslive/
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components import tellduslive
|
||||
|
||||
ATTR_LAST_UPDATED = "time_last_updated"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['tellduslive']
|
||||
|
||||
SENSOR_TYPE_TEMP = "temp"
|
||||
SENSOR_TYPE_HUMIDITY = "humidity"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELCIUS, "mdi:thermometer"],
|
||||
SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"],
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up Tellstick sensors. """
|
||||
sensors = tellduslive.NETWORK.get_sensors()
|
||||
devices = []
|
||||
|
||||
for component in sensors:
|
||||
for sensor in component["data"]:
|
||||
# one component can have more than one sensor
|
||||
# (e.g. both humidity and temperature)
|
||||
devices.append(TelldusLiveSensor(component["id"],
|
||||
component["name"],
|
||||
sensor["name"]))
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
class TelldusLiveSensor(Entity):
|
||||
""" Represents a Telldus Live sensor. """
|
||||
|
||||
def __init__(self, sensor_id, sensor_name, sensor_type):
|
||||
self._sensor_id = sensor_id
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
|
||||
self._last_update = None
|
||||
self._battery_level = 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
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
attrs = dict()
|
||||
if self._battery_level is not None:
|
||||
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
|
||||
if self._last_update is not None:
|
||||
attrs[ATTR_LAST_UPDATED] = self._last_update
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return SENSOR_TYPES[self._sensor_type][1]
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return SENSOR_TYPES[self._sensor_type][2]
|
||||
|
||||
def update(self):
|
||||
values = tellduslive.NETWORK.get_sensor_value(self._sensor_id,
|
||||
self._sensor_type)
|
||||
self._state, self._battery_level, self._last_update = values
|
||||
|
||||
self._state = float(self._state)
|
||||
if self._sensor_type == SENSOR_TYPE_TEMP:
|
||||
self._state = round(self._state, 1)
|
||||
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
|
||||
self._state = int(round(self._state))
|
||||
|
||||
self._battery_level = round(self._battery_level * 100 / 255) # percent
|
||||
|
||||
self._last_update = str(datetime.fromtimestamp(self._last_update))
|
|
@ -27,14 +27,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
sensors.extend([
|
||||
VerisureThermometer(value)
|
||||
for value in verisure.get_climate_status().values()
|
||||
for value in verisure.CLIMATE_STATUS.values()
|
||||
if verisure.SHOW_THERMOMETERS and
|
||||
hasattr(value, 'temperature') and value.temperature
|
||||
])
|
||||
|
||||
sensors.extend([
|
||||
VerisureHygrometer(value)
|
||||
for value in verisure.get_climate_status().values()
|
||||
for value in verisure.CLIMATE_STATUS.values()
|
||||
if verisure.SHOW_HYGROMETERS and
|
||||
hasattr(value, 'humidity') and value.humidity
|
||||
])
|
||||
|
@ -47,20 +47,19 @@ class VerisureThermometer(Entity):
|
|||
|
||||
def __init__(self, climate_status):
|
||||
self._id = climate_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return '{} {}'.format(
|
||||
verisure.STATUS[self._device][self._id].location,
|
||||
verisure.CLIMATE_STATUS[self._id].location,
|
||||
"Temperature")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
# remove ° character
|
||||
return verisure.STATUS[self._device][self._id].temperature[:-1]
|
||||
return verisure.CLIMATE_STATUS[self._id].temperature[:-1]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
|
@ -69,7 +68,7 @@ class VerisureThermometer(Entity):
|
|||
|
||||
def update(self):
|
||||
''' update sensor '''
|
||||
verisure.update()
|
||||
verisure.update_climate()
|
||||
|
||||
|
||||
class VerisureHygrometer(Entity):
|
||||
|
@ -77,20 +76,19 @@ class VerisureHygrometer(Entity):
|
|||
|
||||
def __init__(self, climate_status):
|
||||
self._id = climate_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return '{} {}'.format(
|
||||
verisure.STATUS[self._device][self._id].location,
|
||||
verisure.CLIMATE_STATUS[self._id].location,
|
||||
"Humidity")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
# remove % character
|
||||
return verisure.STATUS[self._device][self._id].humidity[:-1]
|
||||
return verisure.CLIMATE_STATUS[self._id].humidity[:-1]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
|
@ -99,4 +97,4 @@ class VerisureHygrometer(Entity):
|
|||
|
||||
def update(self):
|
||||
''' update sensor '''
|
||||
verisure.update()
|
||||
verisure.update_climate()
|
||||
|
|
|
@ -36,17 +36,17 @@ sensor:
|
|||
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
import urllib.request
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import location, dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REQUIREMENTS = ['xmltodict', 'astral==0.8.1']
|
||||
REQUIREMENTS = ['xmltodict']
|
||||
|
||||
# Sensor types are defined like so:
|
||||
SENSOR_TYPES = {
|
||||
|
@ -73,19 +73,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
from astral import Location, GoogleGeocoder
|
||||
location = Location(('', '', hass.config.latitude, hass.config.longitude,
|
||||
hass.config.time_zone, 0))
|
||||
elevation = config.get('elevation')
|
||||
|
||||
google = GoogleGeocoder()
|
||||
try:
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info(
|
||||
'Retrieved elevation from Google: %s', location.elevation)
|
||||
elevation = location.elevation
|
||||
except urllib.error.URLError:
|
||||
# If no internet connection available etc.
|
||||
elevation = 0
|
||||
if elevation is None:
|
||||
elevation = location.elevation(hass.config.latitude,
|
||||
hass.config.longitude)
|
||||
|
||||
coordinates = dict(lat=hass.config.latitude,
|
||||
lon=hass.config.longitude, msl=elevation)
|
||||
|
@ -116,9 +108,8 @@ class YrSensor(Entity):
|
|||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._weather = weather
|
||||
self._info = ''
|
||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||
self._update = datetime.datetime.fromtimestamp(0)
|
||||
self._update = None
|
||||
|
||||
self.update()
|
||||
|
||||
|
@ -134,14 +125,15 @@ class YrSensor(Entity):
|
|||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns state attributes. """
|
||||
data = {}
|
||||
data[''] = "Weather forecast from yr.no, delivered by the"\
|
||||
" Norwegian Meteorological Institute and the NRK"
|
||||
data = {
|
||||
'about': "Weather forecast from yr.no, delivered by the"
|
||||
" Norwegian Meteorological Institute and the NRK"
|
||||
}
|
||||
if self.type == 'symbol':
|
||||
symbol_nr = self._state
|
||||
data[ATTR_ENTITY_PICTURE] = "http://api.met.no/weatherapi/weathericon/1.1/" \
|
||||
"?symbol=" + str(symbol_nr) + \
|
||||
";content_type=image/png"
|
||||
data[ATTR_ENTITY_PICTURE] = \
|
||||
"http://api.met.no/weatherapi/weathericon/1.1/" \
|
||||
"?symbol={0};content_type=image/png".format(symbol_nr)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -150,76 +142,50 @@ class YrSensor(Entity):
|
|||
""" Unit of measurement of this entity, if any. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Return True if entity has to be polled for state. """
|
||||
return True
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-return-statements
|
||||
def update(self):
|
||||
""" Gets the latest data from yr.no and updates the states. """
|
||||
|
||||
self._weather.update()
|
||||
now = datetime.datetime.now()
|
||||
now = dt_util.utcnow()
|
||||
# check if data should be updated
|
||||
if now <= self._update:
|
||||
if self._update is not None and now <= self._update:
|
||||
return
|
||||
|
||||
time_data = self._weather.data['product']['time']
|
||||
self._weather.update()
|
||||
|
||||
# pylint: disable=consider-using-enumerate
|
||||
# find sensor
|
||||
for k in range(len(time_data)):
|
||||
valid_from = datetime.datetime.strptime(time_data[k]['@from'],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
valid_to = datetime.datetime.strptime(time_data[k]['@to'],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
self._update = valid_to
|
||||
self._info = "Forecast between " + time_data[k]['@from'] \
|
||||
+ " and " + time_data[k]['@to'] + ". "
|
||||
for time_entry in self._weather.data['product']['time']:
|
||||
valid_from = dt_util.str_to_datetime(
|
||||
time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ")
|
||||
valid_to = dt_util.str_to_datetime(
|
||||
time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
temp_data = time_data[k]['location']
|
||||
if self.type not in temp_data and now >= valid_to:
|
||||
loc_data = time_entry['location']
|
||||
|
||||
if self.type not in loc_data or now >= valid_to:
|
||||
continue
|
||||
|
||||
self._update = valid_to
|
||||
|
||||
if self.type == 'precipitation' and valid_from < now:
|
||||
self._state = temp_data[self.type]['@value']
|
||||
return
|
||||
self._state = loc_data[self.type]['@value']
|
||||
break
|
||||
elif self.type == 'symbol' and valid_from < now:
|
||||
self._state = temp_data[self.type]['@number']
|
||||
return
|
||||
elif self.type == 'temperature':
|
||||
self._state = temp_data[self.type]['@value']
|
||||
return
|
||||
self._state = loc_data[self.type]['@number']
|
||||
break
|
||||
elif self.type == ('temperature', 'pressure', 'humidity',
|
||||
'dewpointTemperature'):
|
||||
self._state = loc_data[self.type]['@value']
|
||||
break
|
||||
elif self.type == 'windSpeed':
|
||||
self._state = temp_data[self.type]['@mps']
|
||||
return
|
||||
elif self.type == 'pressure':
|
||||
self._state = temp_data[self.type]['@value']
|
||||
return
|
||||
self._state = loc_data[self.type]['@mps']
|
||||
break
|
||||
elif self.type == 'windDirection':
|
||||
self._state = float(temp_data[self.type]['@deg'])
|
||||
return
|
||||
elif self.type == 'humidity':
|
||||
self._state = temp_data[self.type]['@value']
|
||||
return
|
||||
elif self.type == 'fog':
|
||||
self._state = temp_data[self.type]['@percent']
|
||||
return
|
||||
elif self.type == 'cloudiness':
|
||||
self._state = temp_data[self.type]['@percent']
|
||||
return
|
||||
elif self.type == 'lowClouds':
|
||||
self._state = temp_data[self.type]['@percent']
|
||||
return
|
||||
elif self.type == 'mediumClouds':
|
||||
self._state = temp_data[self.type]['@percent']
|
||||
return
|
||||
elif self.type == 'highClouds':
|
||||
self._state = temp_data[self.type]['@percent']
|
||||
return
|
||||
elif self.type == 'dewpointTemperature':
|
||||
self._state = temp_data[self.type]['@value']
|
||||
return
|
||||
self._state = float(loc_data[self.type]['@deg'])
|
||||
break
|
||||
elif self.type in ('fog', 'cloudiness', 'lowClouds',
|
||||
'mediumClouds', 'highClouds'):
|
||||
self._state = loc_data[self.type]['@percent']
|
||||
break
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
@ -230,17 +196,18 @@ class YrData(object):
|
|||
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
||||
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
||||
|
||||
self._nextrun = datetime.datetime.fromtimestamp(0)
|
||||
self._nextrun = None
|
||||
self.data = {}
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data from yr.no """
|
||||
now = datetime.datetime.now()
|
||||
# check if new will be available
|
||||
if now <= self._nextrun:
|
||||
if self._nextrun is not None and dt_util.utcnow() <= self._nextrun:
|
||||
return
|
||||
try:
|
||||
response = requests.get(self._url)
|
||||
with requests.Session() as sess:
|
||||
response = sess.get(self._url)
|
||||
except requests.RequestException:
|
||||
return
|
||||
if response.status_code != 200:
|
||||
|
@ -252,5 +219,5 @@ class YrData(object):
|
|||
model = self.data['meta']['model']
|
||||
if '@nextrun' not in model:
|
||||
model = model[0]
|
||||
self._nextrun = datetime.datetime.strptime(model['@nextrun'],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
self._nextrun = dt_util.str_to_datetime(model['@nextrun'],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
|
|
@ -8,10 +8,9 @@ https://home-assistant.io/components/sun/
|
|||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import urllib
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util import location as location_util, dt as dt_util
|
||||
from homeassistant.helpers.event import (
|
||||
track_point_in_utc_time, track_utc_time_change)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
@ -111,21 +110,13 @@ def setup(hass, config):
|
|||
platform_config = config.get(DOMAIN, {})
|
||||
|
||||
elevation = platform_config.get(CONF_ELEVATION)
|
||||
if elevation is None:
|
||||
elevation = location_util.elevation(latitude, longitude)
|
||||
|
||||
from astral import Location, GoogleGeocoder
|
||||
from astral import Location
|
||||
|
||||
location = Location(('', '', latitude, longitude, hass.config.time_zone,
|
||||
elevation or 0))
|
||||
|
||||
if elevation is None:
|
||||
google = GoogleGeocoder()
|
||||
try:
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info(
|
||||
'Retrieved elevation from Google: %s', location.elevation)
|
||||
except urllib.error.URLError:
|
||||
# If no internet connection available etc.
|
||||
pass
|
||||
elevation))
|
||||
|
||||
sun = Sun(hass, location)
|
||||
sun.point_in_time_listener(dt_util.utcnow())
|
||||
|
|
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.components import (
|
||||
group, discovery, wink, isy994, verisure, zwave, mysensors)
|
||||
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
||||
|
||||
DOMAIN = 'switch'
|
||||
SCAN_INTERVAL = 30
|
||||
|
@ -40,6 +40,7 @@ DISCOVERY_PLATFORMS = {
|
|||
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||
verisure.DISCOVER_SWITCHES: 'verisure',
|
||||
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
||||
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
"""
|
||||
homeassistant.components.switch.tellduslive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Tellstick switches using Tellstick Net and
|
||||
the Telldus Live online service.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/switch.tellduslive/
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN)
|
||||
from homeassistant.components import tellduslive
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['tellduslive']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Find and return Tellstick switches. """
|
||||
switches = tellduslive.NETWORK.get_switches()
|
||||
add_devices([TelldusLiveSwitch(switch["name"],
|
||||
switch["id"])
|
||||
for switch in switches if switch["type"] == "device"])
|
||||
|
||||
|
||||
class TelldusLiveSwitch(ToggleEntity):
|
||||
""" Represents a Tellstick switch. """
|
||||
|
||||
def __init__(self, name, switch_id):
|
||||
self._name = name
|
||||
self._id = switch_id
|
||||
self._state = STATE_UNKNOWN
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant to poll this entity. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the switch if any. """
|
||||
return self._name
|
||||
|
||||
def update(self):
|
||||
from tellive.live import const
|
||||
state = tellduslive.NETWORK.get_switch_state(self._id)
|
||||
if state == const.TELLSTICK_TURNON:
|
||||
self._state = STATE_ON
|
||||
elif state == const.TELLSTICK_TURNOFF:
|
||||
self._state = STATE_OFF
|
||||
else:
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if switch is on. """
|
||||
return self._state == STATE_ON
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turns the switch on. """
|
||||
if tellduslive.NETWORK.turn_switch_on(self._id):
|
||||
self._state = STATE_ON
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turns the switch off. """
|
||||
if tellduslive.NETWORK.turn_switch_off(self._id):
|
||||
self._state = STATE_OFF
|
||||
self.update_ha_state()
|
|
@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
switches.extend([
|
||||
VerisureSmartplug(value)
|
||||
for value in verisure.get_smartplug_status().values()
|
||||
for value in verisure.SMARTPLUG_STATUS.values()
|
||||
if verisure.SHOW_SMARTPLUGS
|
||||
])
|
||||
|
||||
|
@ -36,31 +36,29 @@ class VerisureSmartplug(SwitchDevice):
|
|||
""" Represents a Verisure smartplug. """
|
||||
def __init__(self, smartplug_status):
|
||||
self._id = smartplug_status.id
|
||||
self.status_on = verisure.MY_PAGES.SMARTPLUG_ON
|
||||
self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Get the name (location) of the smartplug. """
|
||||
return verisure.get_smartplug_status()[self._id].location
|
||||
return verisure.SMARTPLUG_STATUS[self._id].location
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" Returns True if on """
|
||||
plug_status = verisure.get_smartplug_status()[self._id].status
|
||||
return plug_status == self.status_on
|
||||
plug_status = verisure.SMARTPLUG_STATUS[self._id].status
|
||||
return plug_status == 'on'
|
||||
|
||||
def turn_on(self):
|
||||
""" Set smartplug status on. """
|
||||
verisure.MY_PAGES.set_smartplug_status(
|
||||
self._id,
|
||||
self.status_on)
|
||||
verisure.MY_PAGES.smartplug.set(self._id, 'on')
|
||||
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'on')
|
||||
verisure.update_smartplug()
|
||||
|
||||
def turn_off(self):
|
||||
""" Set smartplug status off. """
|
||||
verisure.MY_PAGES.set_smartplug_status(
|
||||
self._id,
|
||||
self.status_off)
|
||||
verisure.MY_PAGES.smartplug.set(self._id, 'off')
|
||||
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'off')
|
||||
verisure.update_smartplug()
|
||||
|
||||
def update(self):
|
||||
verisure.update()
|
||||
verisure.update_smartplug()
|
||||
|
|
|
@ -9,11 +9,14 @@ https://home-assistant.io/components/switch.wemo/
|
|||
import logging
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['pywemo==0.3.3']
|
||||
REQUIREMENTS = ['pywemo==0.3.4']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_WEMO_SUBSCRIPTION_REGISTRY = None
|
||||
|
||||
|
||||
# pylint: disable=unused-argument, too-many-function-args
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
@ -21,6 +24,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
import pywemo
|
||||
import pywemo.discovery as discovery
|
||||
|
||||
global _WEMO_SUBSCRIPTION_REGISTRY
|
||||
if _WEMO_SUBSCRIPTION_REGISTRY is None:
|
||||
_WEMO_SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry()
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.start()
|
||||
|
||||
def stop_wemo(event):
|
||||
""" Shutdown Wemo subscriptions and subscription thread on exit"""
|
||||
_LOGGER.info("Shutting down subscriptions.")
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)
|
||||
|
||||
if discovery_info is not None:
|
||||
location = discovery_info[2]
|
||||
mac = discovery_info[3]
|
||||
|
@ -47,6 +62,23 @@ class WemoSwitch(SwitchDevice):
|
|||
self.insight_params = None
|
||||
self.maker_params = None
|
||||
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.register(wemo)
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.on(
|
||||
wemo, None, self._update_callback)
|
||||
|
||||
def _update_callback(self, _device, _params):
|
||||
""" Called by the wemo device callback to update state. """
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s, sevice=%s',
|
||||
self.name, _device)
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling should be needed with subscriptions """
|
||||
# but leave in for initial version in case of issues.
|
||||
return True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this WeMo switch """
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
"""
|
||||
homeassistant.components.tellduslive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tellduslive Component
|
||||
|
||||
This component adds support for the Telldus Live service.
|
||||
Telldus Live is the online service used with Tellstick Net devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.tellduslive/
|
||||
|
||||
Developer access to the Telldus Live service is neccessary
|
||||
API keys can be aquired from https://api.telldus.com/keys/index
|
||||
|
||||
Tellstick Net devices can be auto discovered using the method described in:
|
||||
https://developer.telldus.com/doxygen/html/TellStickNet.html
|
||||
|
||||
It might be possible to communicate with the Tellstick Net device
|
||||
directly, bypassing the Tellstick Live service.
|
||||
This however is poorly documented and yet not fully supported (?) according to
|
||||
http://developer.telldus.se/ticket/114 and
|
||||
https://developer.telldus.com/doxygen/html/TellStickNet.html
|
||||
|
||||
API requests to certain methods, as described in
|
||||
https://api.telldus.com/explore/sensor/info
|
||||
are limited to one request every 10 minutes
|
||||
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import (
|
||||
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED)
|
||||
|
||||
|
||||
DOMAIN = "tellduslive"
|
||||
DISCOVER_SWITCHES = "tellduslive.switches"
|
||||
DISCOVER_SENSORS = "tellduslive.sensors"
|
||||
|
||||
CONF_PUBLIC_KEY = "public_key"
|
||||
CONF_PRIVATE_KEY = "private_key"
|
||||
CONF_TOKEN = "token"
|
||||
CONF_TOKEN_SECRET = "token_secret"
|
||||
|
||||
REQUIREMENTS = ['tellive-py==0.5.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NETWORK = None
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600)
|
||||
|
||||
|
||||
class TelldusLiveData(object):
|
||||
""" Gets the latest data and update the states. """
|
||||
|
||||
def __init__(self, hass, config):
|
||||
|
||||
public_key = config[DOMAIN].get(CONF_PUBLIC_KEY)
|
||||
private_key = config[DOMAIN].get(CONF_PRIVATE_KEY)
|
||||
token = config[DOMAIN].get(CONF_TOKEN)
|
||||
token_secret = config[DOMAIN].get(CONF_TOKEN_SECRET)
|
||||
|
||||
from tellive.client import LiveClient
|
||||
from tellive.live import TelldusLive
|
||||
|
||||
self._sensors = []
|
||||
self._switches = []
|
||||
|
||||
self._client = LiveClient(public_key=public_key,
|
||||
private_key=private_key,
|
||||
access_token=token,
|
||||
access_secret=token_secret)
|
||||
self._api = TelldusLive(self._client)
|
||||
|
||||
def update(self, hass, config):
|
||||
""" Send discovery event if component not yet discovered """
|
||||
self._update_sensors()
|
||||
self._update_switches()
|
||||
for component_name, found_devices, discovery_type in \
|
||||
(('sensor', self._sensors, DISCOVER_SENSORS),
|
||||
('switch', self._switches, DISCOVER_SWITCHES)):
|
||||
if len(found_devices):
|
||||
component = get_component(component_name)
|
||||
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
|
||||
{ATTR_SERVICE: discovery_type,
|
||||
ATTR_DISCOVERED: {}})
|
||||
|
||||
def _request(self, what, **params):
|
||||
""" Sends a request to the tellstick live API """
|
||||
|
||||
from tellive.live import const
|
||||
|
||||
supported_methods = const.TELLSTICK_TURNON \
|
||||
| const.TELLSTICK_TURNOFF \
|
||||
| const.TELLSTICK_TOGGLE
|
||||
|
||||
default_params = {'supportedMethods': supported_methods,
|
||||
"includeValues": 1,
|
||||
"includeScale": 1}
|
||||
|
||||
params.update(default_params)
|
||||
|
||||
# room for improvement: the telllive library doesn't seem to
|
||||
# re-use sessions, instead it opens a new session for each request
|
||||
# this needs to be fixed
|
||||
response = self._client.request(what, params)
|
||||
return response
|
||||
|
||||
def check_request(self, what, **params):
|
||||
""" Make request, check result if successful """
|
||||
response = self._request(what, **params)
|
||||
return response['status'] == "success"
|
||||
|
||||
def validate_session(self):
|
||||
""" Make a dummy request to see if the session is valid """
|
||||
try:
|
||||
response = self._request("user/profile")
|
||||
return 'email' in response
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_sensors(self):
|
||||
""" Get the latest sensor data from Telldus Live """
|
||||
_LOGGER.info("Updating sensors from Telldus Live")
|
||||
self._sensors = self._request("sensors/list")["sensor"]
|
||||
|
||||
def _update_switches(self):
|
||||
""" Get the configured switches from Telldus Live"""
|
||||
_LOGGER.info("Updating switches from Telldus Live")
|
||||
self._switches = self._request("devices/list")["device"]
|
||||
# filter out any group of switches
|
||||
self._switches = [switch for switch in self._switches
|
||||
if switch["type"] == "device"]
|
||||
|
||||
def get_sensors(self):
|
||||
""" Get the configured sensors """
|
||||
self._update_sensors()
|
||||
return self._sensors
|
||||
|
||||
def get_switches(self):
|
||||
""" Get the configured switches """
|
||||
self._update_switches()
|
||||
return self._switches
|
||||
|
||||
def get_sensor_value(self, sensor_id, sensor_name):
|
||||
""" Get the latest (possibly cached) sensor value """
|
||||
self._update_sensors()
|
||||
for component in self._sensors:
|
||||
if component["id"] == sensor_id:
|
||||
for sensor in component["data"]:
|
||||
if sensor["name"] == sensor_name:
|
||||
return (sensor["value"],
|
||||
component["battery"],
|
||||
component["lastUpdated"])
|
||||
|
||||
def get_switch_state(self, switch_id):
|
||||
""" returns state of switch. """
|
||||
_LOGGER.info("Updating switch state from Telldus Live")
|
||||
response = self._request("device/info", id=switch_id)["state"]
|
||||
return int(response)
|
||||
|
||||
def turn_switch_on(self, switch_id):
|
||||
""" turn switch off """
|
||||
return self.check_request("device/turnOn", id=switch_id)
|
||||
|
||||
def turn_switch_off(self, switch_id):
|
||||
""" turn switch on """
|
||||
return self.check_request("device/turnOff", id=switch_id)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup the tellduslive component """
|
||||
|
||||
# fixme: aquire app key and provide authentication
|
||||
# using username + password
|
||||
if not validate_config(config,
|
||||
{DOMAIN: [CONF_PUBLIC_KEY,
|
||||
CONF_PRIVATE_KEY,
|
||||
CONF_TOKEN,
|
||||
CONF_TOKEN_SECRET]},
|
||||
_LOGGER):
|
||||
_LOGGER.error(
|
||||
"Configuration Error: "
|
||||
"Please make sure you have configured your keys "
|
||||
"that can be aquired from https://api.telldus.com/keys/index")
|
||||
return False
|
||||
|
||||
global NETWORK
|
||||
NETWORK = TelldusLiveData(hass, config)
|
||||
|
||||
if not NETWORK.validate_session():
|
||||
_LOGGER.error(
|
||||
"Authentication Error: "
|
||||
"Please make sure you have configured your keys "
|
||||
"that can be aquired from https://api.telldus.com/keys/index")
|
||||
return False
|
||||
|
||||
NETWORK.update(hass, config)
|
||||
|
||||
return True
|
|
@ -7,6 +7,8 @@ For more details about this component, please refer to the documentation at
|
|||
https://home-assistant.io/components/verisure/
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant import bootstrap
|
||||
|
@ -26,15 +28,14 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
|||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||
|
||||
DEPENDENCIES = ['alarm_control_panel']
|
||||
REQUIREMENTS = [
|
||||
'https://github.com/persandstrom/python-verisure/archive/'
|
||||
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
|
||||
]
|
||||
REQUIREMENTS = ['vsure==0.4.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MY_PAGES = None
|
||||
STATUS = {}
|
||||
ALARM_STATUS = {}
|
||||
SMARTPLUG_STATUS = {}
|
||||
CLIMATE_STATUS = {}
|
||||
|
||||
VERISURE_LOGIN_ERROR = None
|
||||
VERISURE_ERROR = None
|
||||
|
@ -47,7 +48,7 @@ SHOW_SMARTPLUGS = True
|
|||
# if wrong password was given don't try again
|
||||
WRONG_PASSWORD_GIVEN = False
|
||||
|
||||
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5)
|
||||
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=1)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
|
@ -60,10 +61,6 @@ def setup(hass, config):
|
|||
|
||||
from verisure import MyPages, LoginError, Error
|
||||
|
||||
STATUS[MyPages.DEVICE_ALARM] = {}
|
||||
STATUS[MyPages.DEVICE_CLIMATE] = {}
|
||||
STATUS[MyPages.DEVICE_SMARTPLUG] = {}
|
||||
|
||||
global SHOW_THERMOMETERS, SHOW_HYGROMETERS, SHOW_ALARM, SHOW_SMARTPLUGS
|
||||
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
||||
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1'))
|
||||
|
@ -84,7 +81,9 @@ def setup(hass, config):
|
|||
_LOGGER.error('Could not log in to verisure mypages, %s', ex)
|
||||
return False
|
||||
|
||||
update()
|
||||
update_alarm()
|
||||
update_climate()
|
||||
update_smartplug()
|
||||
|
||||
# Load components for the devices in the ISY controller that we support
|
||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||
|
@ -101,24 +100,10 @@ def setup(hass, config):
|
|||
return True
|
||||
|
||||
|
||||
def get_alarm_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_ALARM]
|
||||
|
||||
|
||||
def get_climate_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_CLIMATE]
|
||||
|
||||
|
||||
def get_smartplug_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_SMARTPLUG]
|
||||
|
||||
|
||||
def reconnect():
|
||||
""" Reconnect to verisure mypages. """
|
||||
try:
|
||||
time.sleep(1)
|
||||
MY_PAGES.login()
|
||||
except VERISURE_LOGIN_ERROR as ex:
|
||||
_LOGGER.error("Could not login to Verisure mypages, %s", ex)
|
||||
|
@ -129,19 +114,31 @@ def reconnect():
|
|||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update():
|
||||
def update_alarm():
|
||||
""" Updates the status of alarms. """
|
||||
update_component(MY_PAGES.alarm.get, ALARM_STATUS)
|
||||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update_climate():
|
||||
""" Updates the status of climate sensors. """
|
||||
update_component(MY_PAGES.climate.get, CLIMATE_STATUS)
|
||||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update_smartplug():
|
||||
""" Updates the status of smartplugs. """
|
||||
update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS)
|
||||
|
||||
|
||||
def update_component(get_function, status):
|
||||
""" Updates the status of verisure components. """
|
||||
if WRONG_PASSWORD_GIVEN:
|
||||
_LOGGER.error('Wrong password')
|
||||
return
|
||||
|
||||
try:
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM):
|
||||
STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE):
|
||||
STATUS[MY_PAGES.DEVICE_CLIMATE][overview.id] = overview
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG):
|
||||
STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview
|
||||
except ConnectionError as ex:
|
||||
for overview in get_function():
|
||||
status[overview.id] = overview
|
||||
except (ConnectionError, VERISURE_ERROR) as ex:
|
||||
_LOGGER.error('Caught connection error %s, tries to reconnect', ex)
|
||||
reconnect()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""
|
||||
homeassistant
|
||||
~~~~~~~~~~~~~
|
||||
Core components of Home Assistant.
|
||||
|
||||
Home Assistant is a Home Automation framework for observing the state
|
||||
of entities and react to changes.
|
||||
|
@ -53,9 +52,10 @@ _MockHA = namedtuple("MockHomeAssistant", ['bus'])
|
|||
|
||||
|
||||
class HomeAssistant(object):
|
||||
""" Core class to route all communication to right components. """
|
||||
"""Root object of the Home Assistant home automation."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize new Home Assistant object."""
|
||||
self.pool = pool = create_worker_pool()
|
||||
self.bus = EventBus(pool)
|
||||
self.services = ServiceRegistry(self.bus, pool)
|
||||
|
@ -63,7 +63,7 @@ class HomeAssistant(object):
|
|||
self.config = Config()
|
||||
|
||||
def start(self):
|
||||
""" Start home assistant. """
|
||||
"""Start home assistant."""
|
||||
_LOGGER.info(
|
||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||
|
||||
|
@ -71,12 +71,11 @@ class HomeAssistant(object):
|
|||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
def block_till_stopped(self):
|
||||
""" Will register service homeassistant/stop and
|
||||
will block until called. """
|
||||
"""Register service homeassistant/stop and will block until called."""
|
||||
request_shutdown = threading.Event()
|
||||
|
||||
def stop_homeassistant(*args):
|
||||
""" Stops Home Assistant. """
|
||||
"""Stop Home Assistant."""
|
||||
request_shutdown.set()
|
||||
|
||||
self.services.register(
|
||||
|
@ -98,7 +97,7 @@ class HomeAssistant(object):
|
|||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
""" Stops Home Assistant and shuts down all threads. """
|
||||
"""Stop Home Assistant and shuts down all threads."""
|
||||
_LOGGER.info("Stopping")
|
||||
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||
|
@ -150,8 +149,7 @@ class HomeAssistant(object):
|
|||
|
||||
|
||||
class JobPriority(util.OrderedEnum):
|
||||
""" Provides priorities for bus events. """
|
||||
# pylint: disable=no-init,too-few-public-methods
|
||||
"""Provides job priorities for event bus jobs."""
|
||||
|
||||
EVENT_CALLBACK = 0
|
||||
EVENT_SERVICE = 1
|
||||
|
@ -161,7 +159,7 @@ class JobPriority(util.OrderedEnum):
|
|||
|
||||
@staticmethod
|
||||
def from_event_type(event_type):
|
||||
""" Returns a priority based on event type. """
|
||||
"""Return a priority based on event type."""
|
||||
if event_type == EVENT_TIME_CHANGED:
|
||||
return JobPriority.EVENT_TIME
|
||||
elif event_type == EVENT_STATE_CHANGED:
|
||||
|
@ -175,8 +173,7 @@ class JobPriority(util.OrderedEnum):
|
|||
|
||||
|
||||
class EventOrigin(enum.Enum):
|
||||
""" Distinguish between origin of event. """
|
||||
# pylint: disable=no-init,too-few-public-methods
|
||||
"""Represents origin of an event."""
|
||||
|
||||
local = "LOCAL"
|
||||
remote = "REMOTE"
|
||||
|
@ -185,14 +182,15 @@ class EventOrigin(enum.Enum):
|
|||
return self.value
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Event(object):
|
||||
""" Represents an event within the Bus. """
|
||||
# pylint: disable=too-few-public-methods
|
||||
"""Represents an event within the Bus."""
|
||||
|
||||
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
|
||||
|
||||
def __init__(self, event_type, data=None, origin=EventOrigin.local,
|
||||
time_fired=None):
|
||||
"""Initialize a new event."""
|
||||
self.event_type = event_type
|
||||
self.data = data or {}
|
||||
self.origin = origin
|
||||
|
@ -200,7 +198,7 @@ class Event(object):
|
|||
time_fired or dt_util.utcnow())
|
||||
|
||||
def as_dict(self):
|
||||
""" Returns a dict representation of this Event. """
|
||||
"""Create a dict representation of this Event."""
|
||||
return {
|
||||
'event_type': self.event_type,
|
||||
'data': dict(self.data),
|
||||
|
@ -227,26 +225,23 @@ class Event(object):
|
|||
|
||||
|
||||
class EventBus(object):
|
||||
""" Class that allows different components to communicate via services
|
||||
and events.
|
||||
"""
|
||||
"""Allows firing of and listening for events."""
|
||||
|
||||
def __init__(self, pool=None):
|
||||
"""Initialize a new event bus."""
|
||||
self._listeners = {}
|
||||
self._lock = threading.Lock()
|
||||
self._pool = pool or create_worker_pool()
|
||||
|
||||
@property
|
||||
def listeners(self):
|
||||
""" Dict with events that is being listened for and the number
|
||||
of listeners.
|
||||
"""
|
||||
"""Dict with events and the number of listeners."""
|
||||
with self._lock:
|
||||
return {key: len(self._listeners[key])
|
||||
for key in self._listeners}
|
||||
|
||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
||||
""" Fire an event. """
|
||||
"""Fire an event."""
|
||||
if not self._pool.running:
|
||||
raise HomeAssistantError('Home Assistant has shut down.')
|
||||
|
||||
|
@ -271,7 +266,7 @@ class EventBus(object):
|
|||
self._pool.add_job(job_priority, (func, event))
|
||||
|
||||
def listen(self, event_type, listener):
|
||||
""" Listen for all events or events of a specific type.
|
||||
"""Listen for all events or events of a specific type.
|
||||
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
|
@ -283,7 +278,7 @@ class EventBus(object):
|
|||
self._listeners[event_type] = [listener]
|
||||
|
||||
def listen_once(self, event_type, listener):
|
||||
""" Listen once for event of a specific type.
|
||||
"""Listen once for event of a specific type.
|
||||
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
|
@ -292,7 +287,7 @@ class EventBus(object):
|
|||
"""
|
||||
@ft.wraps(listener)
|
||||
def onetime_listener(event):
|
||||
""" Removes listener from eventbus and then fires listener. """
|
||||
"""Remove listener from eventbus and then fires listener."""
|
||||
if hasattr(onetime_listener, 'run'):
|
||||
return
|
||||
# Set variable so that we will never run twice.
|
||||
|
@ -311,7 +306,7 @@ class EventBus(object):
|
|||
return onetime_listener
|
||||
|
||||
def remove_listener(self, event_type, listener):
|
||||
""" Removes a listener of a specific event_type. """
|
||||
"""Remove a listener of a specific event_type."""
|
||||
with self._lock:
|
||||
try:
|
||||
self._listeners[event_type].remove(listener)
|
||||
|
@ -343,6 +338,7 @@ class State(object):
|
|||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
||||
last_updated=None):
|
||||
"""Initialize a new state."""
|
||||
if not ENTITY_ID_PATTERN.match(entity_id):
|
||||
raise InvalidEntityFormatError((
|
||||
"Invalid entity id encountered: {}. "
|
||||
|
@ -363,31 +359,33 @@ class State(object):
|
|||
|
||||
@property
|
||||
def domain(self):
|
||||
""" Returns domain of this state. """
|
||||
"""Domain of this state."""
|
||||
return util.split_entity_id(self.entity_id)[0]
|
||||
|
||||
@property
|
||||
def object_id(self):
|
||||
""" Returns object_id of this state. """
|
||||
"""Object id of this state."""
|
||||
return util.split_entity_id(self.entity_id)[1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Name to represent this state. """
|
||||
"""Name of this state."""
|
||||
return (
|
||||
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
||||
self.object_id.replace('_', ' '))
|
||||
|
||||
def copy(self):
|
||||
""" Creates a copy of itself. """
|
||||
"""Return a copy of the state."""
|
||||
return State(self.entity_id, self.state,
|
||||
dict(self.attributes), self.last_changed,
|
||||
self.last_updated)
|
||||
|
||||
def as_dict(self):
|
||||
""" Converts State to a dict to be used within JSON.
|
||||
Ensures: state == State.from_dict(state.as_dict()) """
|
||||
"""Return a dict representation of the State.
|
||||
|
||||
To be used for JSON serialization.
|
||||
Ensures: state == State.from_dict(state.as_dict())
|
||||
"""
|
||||
return {'entity_id': self.entity_id,
|
||||
'state': self.state,
|
||||
'attributes': self.attributes,
|
||||
|
@ -396,11 +394,11 @@ class State(object):
|
|||
|
||||
@classmethod
|
||||
def from_dict(cls, json_dict):
|
||||
""" Static method to create a state from a dict.
|
||||
Ensures: state == State.from_json_dict(state.to_json_dict()) """
|
||||
"""Initialize a state from a dict.
|
||||
|
||||
if not (json_dict and
|
||||
'entity_id' in json_dict and
|
||||
Ensures: state == State.from_json_dict(state.to_json_dict())
|
||||
"""
|
||||
if not (json_dict and 'entity_id' in json_dict and
|
||||
'state' in json_dict):
|
||||
return None
|
||||
|
||||
|
@ -433,15 +431,16 @@ class State(object):
|
|||
|
||||
|
||||
class StateMachine(object):
|
||||
""" Helper class that tracks the state of different entities. """
|
||||
"""Helper class that tracks the state of different entities."""
|
||||
|
||||
def __init__(self, bus):
|
||||
"""Initialize state machine."""
|
||||
self._states = {}
|
||||
self._bus = bus
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def entity_ids(self, domain_filter=None):
|
||||
""" List of entity ids that are being tracked. """
|
||||
"""List of entity ids that are being tracked."""
|
||||
if domain_filter is None:
|
||||
return list(self._states.keys())
|
||||
|
||||
|
@ -451,35 +450,36 @@ class StateMachine(object):
|
|||
if state.domain == domain_filter]
|
||||
|
||||
def all(self):
|
||||
""" Returns a list of all states. """
|
||||
"""Create a list of all states."""
|
||||
with self._lock:
|
||||
return [state.copy() for state in self._states.values()]
|
||||
|
||||
def get(self, entity_id):
|
||||
""" Returns the state of the specified entity. """
|
||||
"""Retrieve state of entity_id or None if not found."""
|
||||
state = self._states.get(entity_id.lower())
|
||||
|
||||
# Make a copy so people won't mutate the state
|
||||
return state.copy() if state else None
|
||||
|
||||
def is_state(self, entity_id, state):
|
||||
""" Returns True if entity exists and is specified state. """
|
||||
"""Test if entity exists and is specified state."""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
return (entity_id in self._states and
|
||||
self._states[entity_id].state == state)
|
||||
|
||||
def remove(self, entity_id):
|
||||
""" Removes an entity from the state machine.
|
||||
"""Remove the state of an entity.
|
||||
|
||||
Returns boolean to indicate if an entity was removed. """
|
||||
Returns boolean to indicate if an entity was removed.
|
||||
"""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
with self._lock:
|
||||
return self._states.pop(entity_id, None) is not None
|
||||
|
||||
def set(self, entity_id, new_state, attributes=None):
|
||||
""" Set the state of an entity, add entity if it does not exist.
|
||||
"""Set the state of an entity, add entity if it does not exist.
|
||||
|
||||
Attributes is an optional dict to specify attributes of this state.
|
||||
|
||||
|
@ -514,9 +514,7 @@ class StateMachine(object):
|
|||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||
|
||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
||||
"""
|
||||
DEPRECATED AS OF 8/4/2015
|
||||
"""
|
||||
"""DEPRECATED AS OF 8/4/2015."""
|
||||
_LOGGER.warning(
|
||||
'hass.states.track_change is deprecated. '
|
||||
'Use homeassistant.helpers.event.track_state_change instead.')
|
||||
|
@ -527,33 +525,36 @@ class StateMachine(object):
|
|||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Service(object):
|
||||
""" Represents a service. """
|
||||
"""Represents a callable service."""
|
||||
|
||||
__slots__ = ['func', 'description', 'fields']
|
||||
|
||||
def __init__(self, func, description, fields):
|
||||
"""Initialize a service."""
|
||||
self.func = func
|
||||
self.description = description or ''
|
||||
self.fields = fields or {}
|
||||
|
||||
def as_dict(self):
|
||||
""" Return dictionary representation of this service. """
|
||||
"""Return dictionary representation of this service."""
|
||||
return {
|
||||
'description': self.description,
|
||||
'fields': self.fields,
|
||||
}
|
||||
|
||||
def __call__(self, call):
|
||||
"""Execute the service."""
|
||||
self.func(call)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ServiceCall(object):
|
||||
""" Represents a call to a service. """
|
||||
"""Represents a call to a service."""
|
||||
|
||||
__slots__ = ['domain', 'service', 'data']
|
||||
|
||||
def __init__(self, domain, service, data=None):
|
||||
"""Initialize a service call."""
|
||||
self.domain = domain
|
||||
self.service = service
|
||||
self.data = data or {}
|
||||
|
@ -567,9 +568,10 @@ class ServiceCall(object):
|
|||
|
||||
|
||||
class ServiceRegistry(object):
|
||||
""" Offers services over the eventbus. """
|
||||
"""Offers services over the eventbus."""
|
||||
|
||||
def __init__(self, bus, pool=None):
|
||||
"""Initialize a service registry."""
|
||||
self._services = {}
|
||||
self._lock = threading.Lock()
|
||||
self._pool = pool or create_worker_pool()
|
||||
|
@ -579,14 +581,14 @@ class ServiceRegistry(object):
|
|||
|
||||
@property
|
||||
def services(self):
|
||||
""" Dict with per domain a list of available services. """
|
||||
"""Dict with per domain a list of available services."""
|
||||
with self._lock:
|
||||
return {domain: {key: value.as_dict() for key, value
|
||||
in self._services[domain].items()}
|
||||
for domain in self._services}
|
||||
|
||||
def has_service(self, domain, service):
|
||||
""" Returns True if specified service exists. """
|
||||
"""Test if specified service exists."""
|
||||
return service in self._services.get(domain, [])
|
||||
|
||||
def register(self, domain, service, service_func, description=None):
|
||||
|
@ -611,7 +613,8 @@ class ServiceRegistry(object):
|
|||
|
||||
def call(self, domain, service, service_data=None, blocking=False):
|
||||
"""
|
||||
Calls specified service.
|
||||
Call a service.
|
||||
|
||||
Specify blocking=True to wait till service is executed.
|
||||
Waits a maximum of SERVICE_CALL_LIMIT.
|
||||
|
||||
|
@ -635,10 +638,7 @@ class ServiceRegistry(object):
|
|||
executed_event = threading.Event()
|
||||
|
||||
def service_executed(call):
|
||||
"""
|
||||
Called when a service is executed.
|
||||
Will set the event if matches our service call.
|
||||
"""
|
||||
"""Callback method that is called when service is executed."""
|
||||
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
||||
executed_event.set()
|
||||
|
||||
|
@ -653,7 +653,7 @@ class ServiceRegistry(object):
|
|||
return success
|
||||
|
||||
def _event_to_service_call(self, event):
|
||||
""" Calls a service from an event. """
|
||||
"""Callback for SERVICE_CALLED events from the event bus."""
|
||||
service_data = dict(event.data)
|
||||
domain = service_data.pop(ATTR_DOMAIN, None)
|
||||
service = service_data.pop(ATTR_SERVICE, None)
|
||||
|
@ -670,7 +670,7 @@ class ServiceRegistry(object):
|
|||
(service_handler, service_call)))
|
||||
|
||||
def _execute_service(self, service_and_call):
|
||||
""" Executes a service and fires a SERVICE_EXECUTED event. """
|
||||
"""Execute a service and fires a SERVICE_EXECUTED event."""
|
||||
service, call = service_and_call
|
||||
service(call)
|
||||
|
||||
|
@ -680,16 +680,17 @@ class ServiceRegistry(object):
|
|||
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
|
||||
|
||||
def _generate_unique_id(self):
|
||||
""" Generates a unique service call id. """
|
||||
"""Generate a unique service call id."""
|
||||
self._cur_id += 1
|
||||
return "{}-{}".format(id(self), self._cur_id)
|
||||
|
||||
|
||||
class Config(object):
|
||||
""" Configuration settings for Home Assistant. """
|
||||
"""Configuration settings for Home Assistant."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self):
|
||||
"""Initialize a new config object."""
|
||||
self.latitude = None
|
||||
self.longitude = None
|
||||
self.temperature_unit = None
|
||||
|
@ -709,15 +710,15 @@ class Config(object):
|
|||
self.config_dir = get_default_config_dir()
|
||||
|
||||
def distance(self, lat, lon):
|
||||
""" Calculate distance from Home Assistant in meters. """
|
||||
"""Calculate distance from Home Assistant in meters."""
|
||||
return location.distance(self.latitude, self.longitude, lat, lon)
|
||||
|
||||
def path(self, *path):
|
||||
""" Returns path to the file within the config dir. """
|
||||
"""Generate path to the file within the config dir."""
|
||||
return os.path.join(self.config_dir, *path)
|
||||
|
||||
def temperature(self, value, unit):
|
||||
""" Converts temperature to user preferred unit if set. """
|
||||
"""Convert temperature to user preferred unit if set."""
|
||||
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
|
||||
self.temperature_unit and unit != self.temperature_unit):
|
||||
return value, unit
|
||||
|
@ -732,7 +733,7 @@ class Config(object):
|
|||
self.temperature_unit)
|
||||
|
||||
def as_dict(self):
|
||||
""" Converts config to a dictionary. """
|
||||
"""Create a dict representation of this dict."""
|
||||
time_zone = self.time_zone or dt_util.UTC
|
||||
|
||||
return {
|
||||
|
@ -747,7 +748,7 @@ class Config(object):
|
|||
|
||||
|
||||
def create_timer(hass, interval=TIMER_INTERVAL):
|
||||
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
|
||||
"""Create a timer that will start on HOMEASSISTANT_START."""
|
||||
# We want to be able to fire every time a minute starts (seconds=0).
|
||||
# We want this so other modules can use that to make sure they fire
|
||||
# every minute.
|
||||
|
@ -810,12 +811,12 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
|||
|
||||
|
||||
def create_worker_pool(worker_count=None):
|
||||
""" Creates a worker pool to be used. """
|
||||
"""Create a worker pool."""
|
||||
if worker_count is None:
|
||||
worker_count = MIN_WORKER_THREAD
|
||||
|
||||
def job_handler(job):
|
||||
""" Called whenever a job is available to do. """
|
||||
"""Called whenever a job is available to do."""
|
||||
try:
|
||||
func, arg = job
|
||||
func(arg)
|
||||
|
@ -825,8 +826,7 @@ def create_worker_pool(worker_count=None):
|
|||
_LOGGER.exception("BusHandler:Exception doing job")
|
||||
|
||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
||||
""" Callback to be called when the pool queue gets too big. """
|
||||
|
||||
"""Callback to be called when the pool queue gets too big."""
|
||||
_LOGGER.warning(
|
||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
||||
worker_count, pending_jobs_count)
|
||||
|
|
|
@ -53,8 +53,12 @@ def color_xy_brightness_to_RGB(vX, vY, brightness):
|
|||
return (0, 0, 0)
|
||||
|
||||
Y = brightness
|
||||
X = (Y / vY) * vX
|
||||
Z = (Y / vY) * (1 - vX - vY)
|
||||
if vY != 0:
|
||||
X = (Y / vY) * vX
|
||||
Z = (Y / vY) * (1 - vX - vY)
|
||||
else:
|
||||
X = 0
|
||||
Z = 0
|
||||
|
||||
# Convert to RGB using Wide RGB D65 conversion.
|
||||
r = X * 1.612 - Y * 0.203 - Z * 0.302
|
||||
|
|
|
@ -108,14 +108,14 @@ def datetime_to_date_str(dattim):
|
|||
return dattim.strftime(DATE_STR_FORMAT)
|
||||
|
||||
|
||||
def str_to_datetime(dt_str):
|
||||
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
|
||||
""" Converts a string to a UTC datetime object.
|
||||
|
||||
@rtype: datetime
|
||||
"""
|
||||
try:
|
||||
return dt.datetime.strptime(
|
||||
dt_str, DATETIME_STR_FORMAT).replace(tzinfo=pytz.utc)
|
||||
dt_str, dt_format).replace(tzinfo=pytz.utc)
|
||||
except ValueError: # If dt_str did not match our format
|
||||
return None
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import collections
|
|||
import requests
|
||||
from vincenty import vincenty
|
||||
|
||||
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
||||
|
||||
|
||||
LocationInfo = collections.namedtuple(
|
||||
"LocationInfo",
|
||||
|
@ -34,3 +36,20 @@ def detect_location_info():
|
|||
def distance(lat1, lon1, lat2, lon2):
|
||||
""" Calculate the distance in meters between two points. """
|
||||
return vincenty((lat1, lon1), (lat2, lon2)) * 1000
|
||||
|
||||
|
||||
def elevation(latitude, longitude):
|
||||
""" Return elevation for given latitude and longitude. """
|
||||
|
||||
req = requests.get(ELEVATION_URL, params={
|
||||
'locations': '{},{}'.format(latitude, longitude),
|
||||
'sensor': 'false',
|
||||
})
|
||||
|
||||
if req.status_code != 200:
|
||||
return 0
|
||||
|
||||
try:
|
||||
return int(float(req.json()['results'][0]['elevation']))
|
||||
except (ValueError, KeyError):
|
||||
return 0
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[pytest]
|
||||
testpaths = tests
|
|
@ -1,5 +0,0 @@
|
|||
requests>=2,<3
|
||||
pyyaml>=3.11,<4
|
||||
pytz>=2015.4
|
||||
pip>=7.0.0
|
||||
vincenty==0.1.3
|
|
@ -161,7 +161,6 @@ python-twitch==1.2.0
|
|||
xmltodict
|
||||
|
||||
# homeassistant.components.sun
|
||||
# homeassistant.components.sensor.yr
|
||||
astral==0.8.1
|
||||
|
||||
# homeassistant.components.switch.edimax
|
||||
|
@ -174,7 +173,10 @@ hikvision==0.4
|
|||
orvibo==1.1.0
|
||||
|
||||
# homeassistant.components.switch.wemo
|
||||
pywemo==0.3.3
|
||||
pywemo==0.3.4
|
||||
|
||||
# homeassistant.components.tellduslive
|
||||
tellive-py==0.5.2
|
||||
|
||||
# homeassistant.components.thermostat.heatmiser
|
||||
heatmiserV3==0.9.1
|
||||
|
@ -189,7 +191,7 @@ python-nest==2.6.0
|
|||
radiotherm==1.2
|
||||
|
||||
# homeassistant.components.verisure
|
||||
https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6
|
||||
vsure==0.4.3
|
||||
|
||||
# homeassistant.components.zwave
|
||||
pydispatcher==2.0.5
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
flake8>=2.5.0
|
||||
pylint>=1.5.1
|
||||
coveralls>=1.1
|
||||
pytest>=2.6.4
|
||||
pytest-cov>=2.2.0
|
||||
betamax>=0.5.1
|
|
@ -6,7 +6,7 @@ python3 -m pip install -r requirements_all.txt
|
|||
REQ_STATUS=$?
|
||||
|
||||
echo "Installing development dependencies.."
|
||||
python3 -m pip install flake8 pylint coveralls pytest pytest-cov
|
||||
python3 -m pip install -r requirements_test.txt
|
||||
|
||||
REQ_DEV_STATUS=$?
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ "$TRAVIS_PYTHON_VERSION" != "3.4" ]; then
|
||||
if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then
|
||||
NO_LINT=1
|
||||
fi
|
||||
|
||||
|
|
|
@ -115,7 +115,6 @@ def main():
|
|||
|
||||
if sys.argv[-1] == 'validate':
|
||||
if validate_file(data):
|
||||
print("requirements_all.txt is up to date.")
|
||||
sys.exit(0)
|
||||
print("******* ERROR")
|
||||
print("requirements_all.txt is not up to date")
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "Checking style with flake8..."
|
||||
tput setaf 1
|
||||
flake8 --exclude www_static homeassistant
|
||||
|
||||
FLAKE8_STATUS=$?
|
||||
tput sgr0
|
||||
|
||||
echo "Checking style with pylint..."
|
||||
tput setaf 1
|
||||
pylint homeassistant
|
||||
PYLINT_STATUS=$?
|
||||
tput sgr0
|
||||
|
||||
if [ $FLAKE8_STATUS -eq 0 ]
|
||||
then
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
|
||||
[pep257]
|
||||
ignore = D203,D105
|
5
setup.py
5
setup.py
|
@ -16,7 +16,7 @@ REQUIRES = [
|
|||
'pytz>=2015.4',
|
||||
'pip>=7.0.0',
|
||||
'vincenty==0.1.3',
|
||||
'jinja2>=2.8'
|
||||
'jinja2>=2.8',
|
||||
]
|
||||
|
||||
setup(
|
||||
|
@ -33,6 +33,7 @@ setup(
|
|||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=REQUIRES,
|
||||
test_suite='tests',
|
||||
keywords=['home', 'automation'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
@ -46,5 +47,5 @@ setup(
|
|||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Topic :: Home Automation'
|
||||
]
|
||||
],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import betamax
|
||||
|
||||
with betamax.Betamax.configure() as config:
|
||||
config.cassette_library_dir = 'tests/cassettes'
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -139,3 +139,189 @@ class TestAutomationSun(unittest.TestCase):
|
|||
fire_time_changed(self.hass, trigger_time)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'before': 'sunrise',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_after(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before_with_offset(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'before': 'sunrise',
|
||||
'before_offset': '+1:00:00'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_after_with_offset(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
'after_offset': '+1:00:00'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before_and_after_during(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '10:00:00 16-09-2015',
|
||||
sun.STATE_ATTR_NEXT_SETTING: '15:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
'before': 'sunset'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
|
|
@ -4,66 +4,70 @@ tests.components.sensor.test_yr
|
|||
|
||||
Tests Yr sensor.
|
||||
"""
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.components.sensor as sensor
|
||||
|
||||
|
||||
class TestSensorYr(unittest.TestCase):
|
||||
@pytest.mark.usefixtures('betamax_session')
|
||||
class TestSensorYr:
|
||||
""" Test the Yr sensor. """
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
def setup_method(self, method):
|
||||
self.hass = ha.HomeAssistant()
|
||||
latitude = 32.87336
|
||||
longitude = 117.22743
|
||||
self.hass.config.latitude = 32.87336
|
||||
self.hass.config.longitude = 117.22743
|
||||
|
||||
# Compare it with the real data
|
||||
self.hass.config.latitude = latitude
|
||||
self.hass.config.longitude = longitude
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
def teardown_method(self, method):
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
def test_default_setup(self):
|
||||
self.assertTrue(sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
}
|
||||
}))
|
||||
def test_default_setup(self, betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
assert sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
'elevation': 0,
|
||||
}
|
||||
})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_symbol')
|
||||
|
||||
self.assertTrue(state.state.isnumeric())
|
||||
self.assertEqual(None,
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert state.state.isnumeric()
|
||||
assert state.attributes.get('unit_of_measurement') is None
|
||||
|
||||
def test_custom_setup(self):
|
||||
self.assertTrue(sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
'monitored_conditions': {'pressure', 'windDirection', 'humidity', 'fog', 'windSpeed'}
|
||||
}
|
||||
}))
|
||||
state = self.hass.states.get('sensor.yr_symbol')
|
||||
self.assertEqual(None, state)
|
||||
def test_custom_setup(self, betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
assert sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
'elevation': 0,
|
||||
'monitored_conditions': {
|
||||
'pressure',
|
||||
'windDirection',
|
||||
'humidity',
|
||||
'fog',
|
||||
'windSpeed'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_pressure')
|
||||
self.assertEqual('hPa',
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert 'hPa', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_wind_direction')
|
||||
self.assertEqual('°',
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert '°', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_humidity')
|
||||
self.assertEqual('%',
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert '%', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_fog')
|
||||
self.assertEqual('%',
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert '%', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_wind_speed')
|
||||
self.assertEqual('m/s',
|
||||
state.attributes.get('unit_of_measurement'))
|
||||
assert 'm/s', state.attributes.get('unit_of_measurement')
|
||||
|
|
|
@ -5,11 +5,10 @@ tests.test_component_history
|
|||
Tests the history component.
|
||||
"""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import time
|
||||
from datetime import timedelta
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from datetime import timedelta
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
@ -25,21 +24,24 @@ class TestComponentHistory(unittest.TestCase):
|
|||
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))
|
||||
db_path = self.hass.config.path(recorder.DB_FILE)
|
||||
if os.path.isfile(db_path):
|
||||
os.remove(db_path)
|
||||
|
||||
def init_recorder(self):
|
||||
recorder.setup(self.hass, {})
|
||||
self.hass.start()
|
||||
self.wait_recording_done()
|
||||
|
||||
def wait_recording_done(self):
|
||||
""" Block till recording is done. """
|
||||
self.hass.pool.block_till_done()
|
||||
recorder._INSTANCE.block_till_done()
|
||||
self.init_rec = True
|
||||
|
||||
def test_setup(self):
|
||||
""" Test setup method of history. """
|
||||
|
@ -56,12 +58,11 @@ class TestComponentHistory(unittest.TestCase):
|
|||
for i in range(7):
|
||||
self.hass.states.set(entity_id, "State {}".format(i))
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
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))
|
||||
|
||||
|
@ -70,22 +71,9 @@ class TestComponentHistory(unittest.TestCase):
|
|||
self.init_recorder()
|
||||
states = []
|
||||
|
||||
for i in range(5):
|
||||
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()
|
||||
|
||||
states.append(state)
|
||||
|
||||
recorder._INSTANCE.block_till_done()
|
||||
|
||||
point = dt_util.utcnow() + timedelta(seconds=1)
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=point):
|
||||
now = dt_util.utcnow()
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=now):
|
||||
for i in range(5):
|
||||
state = ha.State(
|
||||
'test.point_in_time_{}'.format(i % 5),
|
||||
|
@ -93,16 +81,32 @@ class TestComponentHistory(unittest.TestCase):
|
|||
{'attribute_test': i})
|
||||
|
||||
mock_state_change_event(self.hass, state)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
states.append(state)
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
future = now + timedelta(seconds=1)
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=future):
|
||||
for i in range(5):
|
||||
state = ha.State(
|
||||
'test.point_in_time_{}'.format(i % 5),
|
||||
"State {}".format(i),
|
||||
{'attribute_test': i})
|
||||
|
||||
mock_state_change_event(self.hass, state)
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
# Get states returns everything before POINT
|
||||
self.assertEqual(states,
|
||||
sorted(history.get_states(point),
|
||||
sorted(history.get_states(future),
|
||||
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))
|
||||
states[0], history.get_state(future, states[0].entity_id))
|
||||
|
||||
def test_state_changes_during_period(self):
|
||||
self.init_recorder()
|
||||
|
@ -110,19 +114,20 @@ class TestComponentHistory(unittest.TestCase):
|
|||
|
||||
def set_state(state):
|
||||
self.hass.states.set(entity_id, state)
|
||||
self.hass.pool.block_till_done()
|
||||
recorder._INSTANCE.block_till_done()
|
||||
|
||||
self.wait_recording_done()
|
||||
return self.hass.states.get(entity_id)
|
||||
|
||||
set_state('idle')
|
||||
set_state('YouTube')
|
||||
|
||||
start = dt_util.utcnow()
|
||||
point = start + timedelta(seconds=1)
|
||||
end = point + timedelta(seconds=1)
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=point):
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=start):
|
||||
set_state('idle')
|
||||
set_state('YouTube')
|
||||
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=point):
|
||||
states = [
|
||||
set_state('idle'),
|
||||
set_state('Netflix'),
|
||||
|
@ -130,10 +135,11 @@ class TestComponentHistory(unittest.TestCase):
|
|||
set_state('YouTube'),
|
||||
]
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=end):
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=end):
|
||||
set_state('Netflix')
|
||||
set_state('Plex')
|
||||
|
||||
self.assertEqual(
|
||||
{entity_id: states},
|
||||
history.state_changes_during_period(start, end, entity_id))
|
||||
hist = history.state_changes_during_period(start, end, entity_id)
|
||||
|
||||
self.assertEqual(states, hist[entity_id])
|
||||
|
|
|
@ -32,6 +32,60 @@ class TestScene(unittest.TestCase):
|
|||
'scene': [[]]
|
||||
}))
|
||||
|
||||
def test_config_yaml_alias_anchor(self):
|
||||
"""
|
||||
Tests the usage of YAML aliases and anchors. The following test scene
|
||||
configuration is equivalent to:
|
||||
|
||||
scene:
|
||||
- name: test
|
||||
entities:
|
||||
light_1: &light_1_state
|
||||
state: 'on'
|
||||
brightness: 100
|
||||
light_2: *light_1_state
|
||||
|
||||
When encountering a YAML alias/anchor, the PyYAML parser will use a
|
||||
reference to the original dictionary, instead of creating a copy, so
|
||||
care needs to be taken to not modify the original.
|
||||
"""
|
||||
test_light = loader.get_component('light.test')
|
||||
test_light.init()
|
||||
|
||||
self.assertTrue(light.setup(self.hass, {
|
||||
light.DOMAIN: {'platform': 'test'}
|
||||
}))
|
||||
|
||||
light_1, light_2 = test_light.DEVICES[0:2]
|
||||
|
||||
light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id])
|
||||
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
entity_state = {
|
||||
'state': 'on',
|
||||
'brightness': 100,
|
||||
}
|
||||
self.assertTrue(scene.setup(self.hass, {
|
||||
'scene': [{
|
||||
'name': 'test',
|
||||
'entities': {
|
||||
light_1.entity_id: entity_state,
|
||||
light_2.entity_id: entity_state,
|
||||
}
|
||||
}]
|
||||
}))
|
||||
|
||||
scene.activate(self.hass, 'scene.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
self.assertTrue(light_1.is_on)
|
||||
self.assertTrue(light_2.is_on)
|
||||
self.assertEqual(100,
|
||||
light_1.last_call('turn_on')[1].get('brightness'))
|
||||
self.assertEqual(100,
|
||||
light_2.last_call('turn_on')[1].get('brightness'))
|
||||
|
||||
def test_activate_scene(self):
|
||||
test_light = loader.get_component('light.test')
|
||||
test_light.init()
|
||||
|
|
Loading…
Reference in New Issue