mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
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/*/modbus.py
|
||||||
|
|
||||||
homeassistant/components/*/tellstick.py
|
homeassistant/components/*/tellstick.py
|
||||||
|
|
||||||
|
homeassistant/components/tellduslive.py
|
||||||
|
homeassistant/components/*/tellduslive.py
|
||||||
|
|
||||||
homeassistant/components/*/vera.py
|
homeassistant/components/*/vera.py
|
||||||
|
|
||||||
homeassistant/components/ecobee.py
|
homeassistant/components/ecobee.py
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -41,6 +41,7 @@ Icon
|
|||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
eggs
|
eggs
|
||||||
|
.eggs
|
||||||
parts
|
parts
|
||||||
bin
|
bin
|
||||||
var
|
var
|
||||||
|
@ -8,8 +8,8 @@ python:
|
|||||||
- 3.4
|
- 3.4
|
||||||
- 3.5
|
- 3.5
|
||||||
install:
|
install:
|
||||||
# Validate requirements_all.txt on Python 3.5
|
# Validate requirements_all.txt on Python 3.4
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then python3 setup.py develop; script/gen_requirements_all.py validate; fi
|
- 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/bootstrap_server
|
||||||
script:
|
script:
|
||||||
- script/cibuild
|
- script/cibuild
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
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
|
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
|
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([
|
alarms.extend([
|
||||||
VerisureAlarm(value)
|
VerisureAlarm(value)
|
||||||
for value in verisure.get_alarm_status().values()
|
for value in verisure.ALARM_STATUS.values()
|
||||||
if verisure.SHOW_ALARM
|
if verisure.SHOW_ALARM
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -42,7 +42,6 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
|||||||
|
|
||||||
def __init__(self, alarm_status):
|
def __init__(self, alarm_status):
|
||||||
self._id = alarm_status.id
|
self._id = alarm_status.id
|
||||||
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
|
||||||
self._state = STATE_UNKNOWN
|
self._state = STATE_UNKNOWN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -62,36 +61,36 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Update alarm status """
|
""" 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
|
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
|
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
|
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(
|
_LOGGER.error(
|
||||||
'Unknown alarm state %s',
|
'Unknown alarm state %s',
|
||||||
verisure.STATUS[self._device][self._id].status)
|
verisure.ALARM_STATUS[self._id].status)
|
||||||
|
|
||||||
def alarm_disarm(self, code=None):
|
def alarm_disarm(self, code=None):
|
||||||
""" Send disarm command. """
|
""" Send disarm command. """
|
||||||
verisure.MY_PAGES.set_alarm_status(
|
verisure.MY_PAGES.alarm.set(code, 'DISARMED')
|
||||||
code,
|
_LOGGER.info('verisure alarm disarming')
|
||||||
verisure.MY_PAGES.ALARM_DISARMED)
|
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||||
_LOGGER.warning('disarming')
|
verisure.update_alarm()
|
||||||
|
|
||||||
def alarm_arm_home(self, code=None):
|
def alarm_arm_home(self, code=None):
|
||||||
""" Send arm home command. """
|
""" Send arm home command. """
|
||||||
verisure.MY_PAGES.set_alarm_status(
|
verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME')
|
||||||
code,
|
_LOGGER.info('verisure alarm arming home')
|
||||||
verisure.MY_PAGES.ALARM_ARMED_HOME)
|
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||||
_LOGGER.warning('arming home')
|
verisure.update_alarm()
|
||||||
|
|
||||||
def alarm_arm_away(self, code=None):
|
def alarm_arm_away(self, code=None):
|
||||||
""" Send arm away command. """
|
""" Send arm away command. """
|
||||||
verisure.MY_PAGES.set_alarm_status(
|
verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY')
|
||||||
code,
|
_LOGGER.info('verisure alarm arming away')
|
||||||
verisure.MY_PAGES.ALARM_ARMED_AWAY)
|
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||||
_LOGGER.warning('arming away')
|
verisure.update_alarm()
|
||||||
|
@ -17,6 +17,10 @@ DEPENDENCIES = ['sun']
|
|||||||
|
|
||||||
CONF_OFFSET = 'offset'
|
CONF_OFFSET = 'offset'
|
||||||
CONF_EVENT = 'event'
|
CONF_EVENT = 'event'
|
||||||
|
CONF_BEFORE = "before"
|
||||||
|
CONF_BEFORE_OFFSET = "before_offset"
|
||||||
|
CONF_AFTER = "after"
|
||||||
|
CONF_AFTER_OFFSET = "after_offset"
|
||||||
|
|
||||||
EVENT_SUNSET = 'sunset'
|
EVENT_SUNSET = 'sunset'
|
||||||
EVENT_SUNRISE = 'sunrise'
|
EVENT_SUNRISE = 'sunrise'
|
||||||
@ -37,26 +41,9 @@ def trigger(hass, config, action):
|
|||||||
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
|
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if CONF_OFFSET in config:
|
offset = _parse_offset(config.get(CONF_OFFSET))
|
||||||
raw_offset = config.get(CONF_OFFSET)
|
if offset is False:
|
||||||
|
return False
|
||||||
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)
|
|
||||||
|
|
||||||
# Do something to call action
|
# Do something to call action
|
||||||
if event == EVENT_SUNRISE:
|
if event == EVENT_SUNRISE:
|
||||||
@ -67,6 +54,65 @@ def trigger(hass, config, action):
|
|||||||
return True
|
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):
|
def trigger_sunrise(hass, action, offset):
|
||||||
""" Trigger action at next sun rise. """
|
""" Trigger action at next sun rise. """
|
||||||
def next_rise():
|
def next_rise():
|
||||||
@ -103,3 +149,26 @@ def trigger_sunset(hass, action, offset):
|
|||||||
action()
|
action()
|
||||||
|
|
||||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
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
|
# Apply an effect to the light, can be EFFECT_COLORLOOP
|
||||||
ATTR_EFFECT = "effect"
|
ATTR_EFFECT = "effect"
|
||||||
EFFECT_COLORLOOP = "colorloop"
|
EFFECT_COLORLOOP = "colorloop"
|
||||||
|
EFFECT_RANDOM = "random"
|
||||||
EFFECT_WHITE = "white"
|
EFFECT_WHITE = "white"
|
||||||
|
|
||||||
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||||
@ -228,7 +229,8 @@ def setup(hass, config):
|
|||||||
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
|
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
|
||||||
params[ATTR_FLASH] = dat[ATTR_FLASH]
|
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]
|
params[ATTR_EFFECT] = dat[ATTR_EFFECT]
|
||||||
|
|
||||||
for light in target_lights:
|
for light in target_lights:
|
||||||
|
@ -10,6 +10,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME
|
|||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
|
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
|
||||||
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
|
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']
|
REQUIREMENTS = ['phue==0.8']
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
@ -233,6 +234,9 @@ class HueLight(Light):
|
|||||||
|
|
||||||
if effect == EFFECT_COLORLOOP:
|
if effect == EFFECT_COLORLOOP:
|
||||||
command['effect'] = 'colorloop'
|
command['effect'] = 'colorloop'
|
||||||
|
elif effect == EFFECT_RANDOM:
|
||||||
|
command['hue'] = random.randrange(0, 65535)
|
||||||
|
command['sat'] = random.randrange(150, 254)
|
||||||
else:
|
else:
|
||||||
command['effect'] = 'none'
|
command['effect'] = 'none'
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ turn_on:
|
|||||||
description: Light effect
|
description: Light effect
|
||||||
values:
|
values:
|
||||||
- colorloop
|
- colorloop
|
||||||
|
- random
|
||||||
|
|
||||||
turn_off:
|
turn_off:
|
||||||
description: Turn a light off
|
description: Turn a light off
|
||||||
|
@ -30,7 +30,7 @@ DEFAULT_QOS = 0
|
|||||||
DEFAULT_RETAIN = False
|
DEFAULT_RETAIN = False
|
||||||
|
|
||||||
SERVICE_PUBLISH = 'publish'
|
SERVICE_PUBLISH = 'publish'
|
||||||
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
|
||||||
|
|
||||||
REQUIREMENTS = ['paho-mqtt==1.1']
|
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import json
|
|||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from homeassistant.core import Event, EventOrigin, State
|
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.remote import JSONEncoder
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||||
@ -62,8 +62,8 @@ def row_to_state(row):
|
|||||||
try:
|
try:
|
||||||
return State(
|
return State(
|
||||||
row[1], row[2], json.loads(row[3]),
|
row[1], row[2], json.loads(row[3]),
|
||||||
date_util.utc_from_timestamp(row[4]),
|
dt_util.utc_from_timestamp(row[4]),
|
||||||
date_util.utc_from_timestamp(row[5]))
|
dt_util.utc_from_timestamp(row[5]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to state: %s", row)
|
_LOGGER.exception("Error converting row to state: %s", row)
|
||||||
@ -74,7 +74,7 @@ def row_to_event(row):
|
|||||||
""" Convert a databse row to an event. """
|
""" Convert a databse row to an event. """
|
||||||
try:
|
try:
|
||||||
return Event(row[1], json.loads(row[2]), EventOrigin(row[3]),
|
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:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to event: %s", row)
|
_LOGGER.exception("Error converting row to event: %s", row)
|
||||||
@ -116,10 +116,10 @@ class RecorderRun(object):
|
|||||||
self.start = _INSTANCE.recording_start
|
self.start = _INSTANCE.recording_start
|
||||||
self.closed_incorrect = False
|
self.closed_incorrect = False
|
||||||
else:
|
else:
|
||||||
self.start = date_util.utc_from_timestamp(row[1])
|
self.start = dt_util.utc_from_timestamp(row[1])
|
||||||
|
|
||||||
if row[2] is not None:
|
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])
|
self.closed_incorrect = bool(row[3])
|
||||||
|
|
||||||
@ -169,8 +169,8 @@ class Recorder(threading.Thread):
|
|||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
self.quit_object = object()
|
self.quit_object = object()
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.recording_start = date_util.utcnow()
|
self.recording_start = dt_util.utcnow()
|
||||||
self.utc_offset = date_util.now().utcoffset().total_seconds()
|
self.utc_offset = dt_util.now().utcoffset().total_seconds()
|
||||||
|
|
||||||
def start_recording(event):
|
def start_recording(event):
|
||||||
""" Start recording. """
|
""" Start recording. """
|
||||||
@ -217,10 +217,11 @@ class Recorder(threading.Thread):
|
|||||||
def shutdown(self, event):
|
def shutdown(self, event):
|
||||||
""" Tells the recorder to shut down. """
|
""" Tells the recorder to shut down. """
|
||||||
self.queue.put(self.quit_object)
|
self.queue.put(self.quit_object)
|
||||||
|
self.block_till_done()
|
||||||
|
|
||||||
def record_state(self, entity_id, state, event_id):
|
def record_state(self, entity_id, state, event_id):
|
||||||
""" Save a state to the database. """
|
""" Save a state to the database. """
|
||||||
now = date_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
# State got deleted
|
# State got deleted
|
||||||
if state is None:
|
if state is None:
|
||||||
@ -247,7 +248,7 @@ class Recorder(threading.Thread):
|
|||||||
""" Save an event to the database. """
|
""" Save an event to the database. """
|
||||||
info = (
|
info = (
|
||||||
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
||||||
str(event.origin), date_util.utcnow(), event.time_fired,
|
str(event.origin), dt_util.utcnow(), event.time_fired,
|
||||||
self.utc_offset
|
self.utc_offset
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -307,7 +308,7 @@ class Recorder(threading.Thread):
|
|||||||
def save_migration(migration_id):
|
def save_migration(migration_id):
|
||||||
""" Save and commit a migration to the database. """
|
""" Save and commit a migration to the database. """
|
||||||
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
||||||
(migration_id, date_util.utcnow()))
|
(migration_id, dt_util.utcnow()))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||||
|
|
||||||
@ -420,18 +421,18 @@ class Recorder(threading.Thread):
|
|||||||
self.query(
|
self.query(
|
||||||
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
||||||
VALUES (?, ?, ?)""",
|
VALUES (?, ?, ?)""",
|
||||||
(self.recording_start, date_util.utcnow(), self.utc_offset))
|
(self.recording_start, dt_util.utcnow(), self.utc_offset))
|
||||||
|
|
||||||
def _close_run(self):
|
def _close_run(self):
|
||||||
""" Save end time for current run. """
|
""" Save end time for current run. """
|
||||||
self.query(
|
self.query(
|
||||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||||
(date_util.utcnow(), self.recording_start))
|
(dt_util.utcnow(), self.recording_start))
|
||||||
|
|
||||||
|
|
||||||
def _adapt_datetime(datetimestamp):
|
def _adapt_datetime(datetimestamp):
|
||||||
""" Turn a datetime into an integer for in the DB. """
|
""" Turn a datetime into an integer for in the DB. """
|
||||||
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||||
|
|
||||||
|
|
||||||
def _verify_instance():
|
def _verify_instance():
|
||||||
|
@ -73,8 +73,9 @@ def _process_config(scene_config):
|
|||||||
|
|
||||||
for entity_id in c_entities:
|
for entity_id in c_entities:
|
||||||
if isinstance(c_entities[entity_id], dict):
|
if isinstance(c_entities[entity_id], dict):
|
||||||
state = c_entities[entity_id].pop('state', None)
|
entity_attrs = c_entities[entity_id].copy()
|
||||||
attributes = c_entities[entity_id]
|
state = entity_attrs.pop('state', None)
|
||||||
|
attributes = entity_attrs
|
||||||
else:
|
else:
|
||||||
state = c_entities[entity_id]
|
state = c_entities[entity_id]
|
||||||
attributes = {}
|
attributes = {}
|
||||||
|
@ -10,7 +10,7 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
wink, zwave, isy994, verisure, ecobee, mysensors)
|
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors)
|
||||||
|
|
||||||
DOMAIN = 'sensor'
|
DOMAIN = 'sensor'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@ -24,6 +24,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
isy994.DISCOVER_SENSORS: 'isy994',
|
isy994.DISCOVER_SENSORS: 'isy994',
|
||||||
verisure.DISCOVER_SENSORS: 'verisure',
|
verisure.DISCOVER_SENSORS: 'verisure',
|
||||||
ecobee.DISCOVER_SENSORS: 'ecobee',
|
ecobee.DISCOVER_SENSORS: 'ecobee',
|
||||||
|
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
||||||
mysensors.DISCOVER_SENSORS: 'mysensors',
|
mysensors.DISCOVER_SENSORS: 'mysensors',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,11 @@ class EliqSensor(Entity):
|
|||||||
""" Returns the name. """
|
""" Returns the name. """
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
""" Returns icon. """
|
||||||
|
return "mdi:speedometer"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
""" Unit of measurement of this entity, if any. """
|
""" Unit of measurement of this entity, if any. """
|
||||||
|
@ -102,13 +102,13 @@ class RestSensor(Entity):
|
|||||||
self.rest.update()
|
self.rest.update()
|
||||||
value = self.rest.data
|
value = self.rest.data
|
||||||
|
|
||||||
if 'error' in value:
|
if value is None:
|
||||||
self._state = value['error']
|
value = STATE_UNKNOWN
|
||||||
else:
|
elif self._value_template is not None:
|
||||||
if self._value_template is not None:
|
value = template.render_with_possible_json_value(
|
||||||
value = template.render_with_possible_json_value(
|
self._hass, self._value_template, value, STATE_UNKNOWN)
|
||||||
self._hass, self._value_template, value, STATE_UNKNOWN)
|
|
||||||
self._state = value
|
self._state = value
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -118,7 +118,7 @@ class RestDataGet(object):
|
|||||||
def __init__(self, resource, verify_ssl):
|
def __init__(self, resource, verify_ssl):
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
self._verify_ssl = verify_ssl
|
self._verify_ssl = verify_ssl
|
||||||
self.data = dict()
|
self.data = None
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
@ -126,12 +126,10 @@ class RestDataGet(object):
|
|||||||
try:
|
try:
|
||||||
response = requests.get(self._resource, timeout=10,
|
response = requests.get(self._resource, timeout=10,
|
||||||
verify=self._verify_ssl)
|
verify=self._verify_ssl)
|
||||||
if 'error' in self.data:
|
|
||||||
del self.data['error']
|
|
||||||
self.data = response.text
|
self.data = response.text
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
||||||
self.data['error'] = STATE_UNKNOWN
|
self.data = None
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -142,7 +140,7 @@ class RestDataPost(object):
|
|||||||
self._resource = resource
|
self._resource = resource
|
||||||
self._payload = payload
|
self._payload = payload
|
||||||
self._verify_ssl = verify_ssl
|
self._verify_ssl = verify_ssl
|
||||||
self.data = dict()
|
self.data = None
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
@ -150,9 +148,7 @@ class RestDataPost(object):
|
|||||||
try:
|
try:
|
||||||
response = requests.post(self._resource, data=self._payload,
|
response = requests.post(self._resource, data=self._payload,
|
||||||
timeout=10, verify=self._verify_ssl)
|
timeout=10, verify=self._verify_ssl)
|
||||||
if 'error' in self.data:
|
|
||||||
del self.data['error']
|
|
||||||
self.data = response.text
|
self.data = response.text
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
_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']
|
REQUIREMENTS = ['psutil==3.2.2']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'disk_use_percent': ['Disk Use', '%'],
|
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||||
'disk_use': ['Disk Use', 'GiB'],
|
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||||
'disk_free': ['Disk Free', 'GiB'],
|
'disk_free': ['Disk Free', 'GiB', 'mdi:harddisk'],
|
||||||
'memory_use_percent': ['RAM Use', '%'],
|
'memory_use_percent': ['RAM Use', '%', 'mdi:memory'],
|
||||||
'memory_use': ['RAM Use', 'MiB'],
|
'memory_use': ['RAM Use', 'MiB', 'mdi:memory'],
|
||||||
'memory_free': ['RAM Free', 'MiB'],
|
'memory_free': ['RAM Free', 'MiB', 'mdi:memory'],
|
||||||
'processor_use': ['CPU Use', '%'],
|
'processor_use': ['CPU Use', '%', 'mdi:memory'],
|
||||||
'process': ['Process', ''],
|
'process': ['Process', '', 'mdi:memory'],
|
||||||
'swap_use_percent': ['Swap Use', '%'],
|
'swap_use_percent': ['Swap Use', '%', 'mdi:harddisk'],
|
||||||
'swap_use': ['Swap Use', 'GiB'],
|
'swap_use': ['Swap Use', 'GiB', 'mdi:harddisk'],
|
||||||
'swap_free': ['Swap Free', 'GiB'],
|
'swap_free': ['Swap Free', 'GiB', 'mdi:harddisk'],
|
||||||
'network_out': ['Sent', 'MiB'],
|
'network_out': ['Sent', 'MiB', 'mdi:server-network'],
|
||||||
'network_in': ['Recieved', 'MiB'],
|
'network_in': ['Recieved', 'MiB', 'mdi:server-network'],
|
||||||
'packets_out': ['Packets sent', ''],
|
'packets_out': ['Packets sent', '', 'mdi:server-network'],
|
||||||
'packets_in': ['Packets recieved', ''],
|
'packets_in': ['Packets recieved', '', 'mdi:server-network'],
|
||||||
'ipv4_address': ['IPv4 address', ''],
|
'ipv4_address': ['IPv4 address', '', 'mdi:server-network'],
|
||||||
'ipv6_address': ['IPv6 address', ''],
|
'ipv6_address': ['IPv6 address', '', 'mdi:server-network'],
|
||||||
'last_boot': ['Last Boot', ''],
|
'last_boot': ['Last Boot', '', 'mdi:clock'],
|
||||||
'since_last_boot': ['Since Last Boot', '']
|
'since_last_boot': ['Since Last Boot', '', 'mdi:clock']
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -69,6 +69,10 @@ class SystemMonitorSensor(Entity):
|
|||||||
def name(self):
|
def name(self):
|
||||||
return self._name.rstrip()
|
return self._name.rstrip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
return SENSOR_TYPES[self.type][2]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
|
99
homeassistant/components/sensor/tellduslive.py
Normal file
99
homeassistant/components/sensor/tellduslive.py
Normal file
@ -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([
|
sensors.extend([
|
||||||
VerisureThermometer(value)
|
VerisureThermometer(value)
|
||||||
for value in verisure.get_climate_status().values()
|
for value in verisure.CLIMATE_STATUS.values()
|
||||||
if verisure.SHOW_THERMOMETERS and
|
if verisure.SHOW_THERMOMETERS and
|
||||||
hasattr(value, 'temperature') and value.temperature
|
hasattr(value, 'temperature') and value.temperature
|
||||||
])
|
])
|
||||||
|
|
||||||
sensors.extend([
|
sensors.extend([
|
||||||
VerisureHygrometer(value)
|
VerisureHygrometer(value)
|
||||||
for value in verisure.get_climate_status().values()
|
for value in verisure.CLIMATE_STATUS.values()
|
||||||
if verisure.SHOW_HYGROMETERS and
|
if verisure.SHOW_HYGROMETERS and
|
||||||
hasattr(value, 'humidity') and value.humidity
|
hasattr(value, 'humidity') and value.humidity
|
||||||
])
|
])
|
||||||
@ -47,20 +47,19 @@ class VerisureThermometer(Entity):
|
|||||||
|
|
||||||
def __init__(self, climate_status):
|
def __init__(self, climate_status):
|
||||||
self._id = climate_status.id
|
self._id = climate_status.id
|
||||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" Returns the name of the device. """
|
""" Returns the name of the device. """
|
||||||
return '{} {}'.format(
|
return '{} {}'.format(
|
||||||
verisure.STATUS[self._device][self._id].location,
|
verisure.CLIMATE_STATUS[self._id].location,
|
||||||
"Temperature")
|
"Temperature")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
# remove ° character
|
# remove ° character
|
||||||
return verisure.STATUS[self._device][self._id].temperature[:-1]
|
return verisure.CLIMATE_STATUS[self._id].temperature[:-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@ -69,7 +68,7 @@ class VerisureThermometer(Entity):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
''' update sensor '''
|
''' update sensor '''
|
||||||
verisure.update()
|
verisure.update_climate()
|
||||||
|
|
||||||
|
|
||||||
class VerisureHygrometer(Entity):
|
class VerisureHygrometer(Entity):
|
||||||
@ -77,20 +76,19 @@ class VerisureHygrometer(Entity):
|
|||||||
|
|
||||||
def __init__(self, climate_status):
|
def __init__(self, climate_status):
|
||||||
self._id = climate_status.id
|
self._id = climate_status.id
|
||||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" Returns the name of the device. """
|
""" Returns the name of the device. """
|
||||||
return '{} {}'.format(
|
return '{} {}'.format(
|
||||||
verisure.STATUS[self._device][self._id].location,
|
verisure.CLIMATE_STATUS[self._id].location,
|
||||||
"Humidity")
|
"Humidity")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
# remove % character
|
# remove % character
|
||||||
return verisure.STATUS[self._device][self._id].humidity[:-1]
|
return verisure.CLIMATE_STATUS[self._id].humidity[:-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@ -99,4 +97,4 @@ class VerisureHygrometer(Entity):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
''' update sensor '''
|
''' update sensor '''
|
||||||
verisure.update()
|
verisure.update_climate()
|
||||||
|
@ -36,17 +36,17 @@ sensor:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
|
||||||
import urllib.request
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.util import location, dt as dt_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['xmltodict', 'astral==0.8.1']
|
REQUIREMENTS = ['xmltodict']
|
||||||
|
|
||||||
# Sensor types are defined like so:
|
# Sensor types are defined like so:
|
||||||
SENSOR_TYPES = {
|
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")
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from astral import Location, GoogleGeocoder
|
elevation = config.get('elevation')
|
||||||
location = Location(('', '', hass.config.latitude, hass.config.longitude,
|
|
||||||
hass.config.time_zone, 0))
|
|
||||||
|
|
||||||
google = GoogleGeocoder()
|
if elevation is None:
|
||||||
try:
|
elevation = location.elevation(hass.config.latitude,
|
||||||
google._get_elevation(location) # pylint: disable=protected-access
|
hass.config.longitude)
|
||||||
_LOGGER.info(
|
|
||||||
'Retrieved elevation from Google: %s', location.elevation)
|
|
||||||
elevation = location.elevation
|
|
||||||
except urllib.error.URLError:
|
|
||||||
# If no internet connection available etc.
|
|
||||||
elevation = 0
|
|
||||||
|
|
||||||
coordinates = dict(lat=hass.config.latitude,
|
coordinates = dict(lat=hass.config.latitude,
|
||||||
lon=hass.config.longitude, msl=elevation)
|
lon=hass.config.longitude, msl=elevation)
|
||||||
@ -116,9 +108,8 @@ class YrSensor(Entity):
|
|||||||
self.type = sensor_type
|
self.type = sensor_type
|
||||||
self._state = None
|
self._state = None
|
||||||
self._weather = weather
|
self._weather = weather
|
||||||
self._info = ''
|
|
||||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||||
self._update = datetime.datetime.fromtimestamp(0)
|
self._update = None
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -134,14 +125,15 @@ class YrSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns state attributes. """
|
""" Returns state attributes. """
|
||||||
data = {}
|
data = {
|
||||||
data[''] = "Weather forecast from yr.no, delivered by the"\
|
'about': "Weather forecast from yr.no, delivered by the"
|
||||||
" Norwegian Meteorological Institute and the NRK"
|
" Norwegian Meteorological Institute and the NRK"
|
||||||
|
}
|
||||||
if self.type == 'symbol':
|
if self.type == 'symbol':
|
||||||
symbol_nr = self._state
|
symbol_nr = self._state
|
||||||
data[ATTR_ENTITY_PICTURE] = "http://api.met.no/weatherapi/weathericon/1.1/" \
|
data[ATTR_ENTITY_PICTURE] = \
|
||||||
"?symbol=" + str(symbol_nr) + \
|
"http://api.met.no/weatherapi/weathericon/1.1/" \
|
||||||
";content_type=image/png"
|
"?symbol={0};content_type=image/png".format(symbol_nr)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -150,76 +142,50 @@ class YrSensor(Entity):
|
|||||||
""" Unit of measurement of this entity, if any. """
|
""" Unit of measurement of this entity, if any. """
|
||||||
return self._unit_of_measurement
|
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):
|
def update(self):
|
||||||
""" Gets the latest data from yr.no and updates the states. """
|
""" Gets the latest data from yr.no and updates the states. """
|
||||||
|
|
||||||
self._weather.update()
|
now = dt_util.utcnow()
|
||||||
now = datetime.datetime.now()
|
|
||||||
# check if data should be updated
|
# check if data should be updated
|
||||||
if now <= self._update:
|
if self._update is not None and now <= self._update:
|
||||||
return
|
return
|
||||||
|
|
||||||
time_data = self._weather.data['product']['time']
|
self._weather.update()
|
||||||
|
|
||||||
# pylint: disable=consider-using-enumerate
|
|
||||||
# find sensor
|
# find sensor
|
||||||
for k in range(len(time_data)):
|
for time_entry in self._weather.data['product']['time']:
|
||||||
valid_from = datetime.datetime.strptime(time_data[k]['@from'],
|
valid_from = dt_util.str_to_datetime(
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
valid_to = datetime.datetime.strptime(time_data[k]['@to'],
|
valid_to = dt_util.str_to_datetime(
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
self._update = valid_to
|
|
||||||
self._info = "Forecast between " + time_data[k]['@from'] \
|
|
||||||
+ " and " + time_data[k]['@to'] + ". "
|
|
||||||
|
|
||||||
temp_data = time_data[k]['location']
|
loc_data = time_entry['location']
|
||||||
if self.type not in temp_data and now >= valid_to:
|
|
||||||
|
if self.type not in loc_data or now >= valid_to:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
self._update = valid_to
|
||||||
|
|
||||||
if self.type == 'precipitation' and valid_from < now:
|
if self.type == 'precipitation' and valid_from < now:
|
||||||
self._state = temp_data[self.type]['@value']
|
self._state = loc_data[self.type]['@value']
|
||||||
return
|
break
|
||||||
elif self.type == 'symbol' and valid_from < now:
|
elif self.type == 'symbol' and valid_from < now:
|
||||||
self._state = temp_data[self.type]['@number']
|
self._state = loc_data[self.type]['@number']
|
||||||
return
|
break
|
||||||
elif self.type == 'temperature':
|
elif self.type == ('temperature', 'pressure', 'humidity',
|
||||||
self._state = temp_data[self.type]['@value']
|
'dewpointTemperature'):
|
||||||
return
|
self._state = loc_data[self.type]['@value']
|
||||||
|
break
|
||||||
elif self.type == 'windSpeed':
|
elif self.type == 'windSpeed':
|
||||||
self._state = temp_data[self.type]['@mps']
|
self._state = loc_data[self.type]['@mps']
|
||||||
return
|
break
|
||||||
elif self.type == 'pressure':
|
|
||||||
self._state = temp_data[self.type]['@value']
|
|
||||||
return
|
|
||||||
elif self.type == 'windDirection':
|
elif self.type == 'windDirection':
|
||||||
self._state = float(temp_data[self.type]['@deg'])
|
self._state = float(loc_data[self.type]['@deg'])
|
||||||
return
|
break
|
||||||
elif self.type == 'humidity':
|
elif self.type in ('fog', 'cloudiness', 'lowClouds',
|
||||||
self._state = temp_data[self.type]['@value']
|
'mediumClouds', 'highClouds'):
|
||||||
return
|
self._state = loc_data[self.type]['@percent']
|
||||||
elif self.type == 'fog':
|
break
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -230,17 +196,18 @@ class YrData(object):
|
|||||||
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
||||||
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
||||||
|
|
||||||
self._nextrun = datetime.datetime.fromtimestamp(0)
|
self._nextrun = None
|
||||||
|
self.data = {}
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest data from yr.no """
|
""" Gets the latest data from yr.no """
|
||||||
now = datetime.datetime.now()
|
|
||||||
# check if new will be available
|
# check if new will be available
|
||||||
if now <= self._nextrun:
|
if self._nextrun is not None and dt_util.utcnow() <= self._nextrun:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
response = requests.get(self._url)
|
with requests.Session() as sess:
|
||||||
|
response = sess.get(self._url)
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
return
|
return
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
@ -252,5 +219,5 @@ class YrData(object):
|
|||||||
model = self.data['meta']['model']
|
model = self.data['meta']['model']
|
||||||
if '@nextrun' not in model:
|
if '@nextrun' not in model:
|
||||||
model = model[0]
|
model = model[0]
|
||||||
self._nextrun = datetime.datetime.strptime(model['@nextrun'],
|
self._nextrun = dt_util.str_to_datetime(model['@nextrun'],
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
@ -8,10 +8,9 @@ https://home-assistant.io/components/sun/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import urllib
|
|
||||||
|
|
||||||
import homeassistant.util as util
|
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 (
|
from homeassistant.helpers.event import (
|
||||||
track_point_in_utc_time, track_utc_time_change)
|
track_point_in_utc_time, track_utc_time_change)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@ -111,21 +110,13 @@ def setup(hass, config):
|
|||||||
platform_config = config.get(DOMAIN, {})
|
platform_config = config.get(DOMAIN, {})
|
||||||
|
|
||||||
elevation = platform_config.get(CONF_ELEVATION)
|
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,
|
location = Location(('', '', latitude, longitude, hass.config.time_zone,
|
||||||
elevation or 0))
|
elevation))
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
sun = Sun(hass, location)
|
sun = Sun(hass, location)
|
||||||
sun.point_in_time_listener(dt_util.utcnow())
|
sun.point_in_time_listener(dt_util.utcnow())
|
||||||
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
group, discovery, wink, isy994, verisure, zwave, mysensors)
|
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@ -40,6 +40,7 @@ DISCOVERY_PLATFORMS = {
|
|||||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||||
verisure.DISCOVER_SWITCHES: 'verisure',
|
verisure.DISCOVER_SWITCHES: 'verisure',
|
||||||
zwave.DISCOVER_SWITCHES: 'zwave',
|
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||||
|
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
||||||
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
mysensors.DISCOVER_SWITCHES: 'mysensors',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
73
homeassistant/components/switch/tellduslive.py
Normal file
73
homeassistant/components/switch/tellduslive.py
Normal file
@ -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([
|
switches.extend([
|
||||||
VerisureSmartplug(value)
|
VerisureSmartplug(value)
|
||||||
for value in verisure.get_smartplug_status().values()
|
for value in verisure.SMARTPLUG_STATUS.values()
|
||||||
if verisure.SHOW_SMARTPLUGS
|
if verisure.SHOW_SMARTPLUGS
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -36,31 +36,29 @@ class VerisureSmartplug(SwitchDevice):
|
|||||||
""" Represents a Verisure smartplug. """
|
""" Represents a Verisure smartplug. """
|
||||||
def __init__(self, smartplug_status):
|
def __init__(self, smartplug_status):
|
||||||
self._id = smartplug_status.id
|
self._id = smartplug_status.id
|
||||||
self.status_on = verisure.MY_PAGES.SMARTPLUG_ON
|
|
||||||
self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" Get the name (location) of the smartplug. """
|
""" Get the name (location) of the smartplug. """
|
||||||
return verisure.get_smartplug_status()[self._id].location
|
return verisure.SMARTPLUG_STATUS[self._id].location
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
""" Returns True if on """
|
""" Returns True if on """
|
||||||
plug_status = verisure.get_smartplug_status()[self._id].status
|
plug_status = verisure.SMARTPLUG_STATUS[self._id].status
|
||||||
return plug_status == self.status_on
|
return plug_status == 'on'
|
||||||
|
|
||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
""" Set smartplug status on. """
|
""" Set smartplug status on. """
|
||||||
verisure.MY_PAGES.set_smartplug_status(
|
verisure.MY_PAGES.smartplug.set(self._id, 'on')
|
||||||
self._id,
|
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'on')
|
||||||
self.status_on)
|
verisure.update_smartplug()
|
||||||
|
|
||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
""" Set smartplug status off. """
|
""" Set smartplug status off. """
|
||||||
verisure.MY_PAGES.set_smartplug_status(
|
verisure.MY_PAGES.smartplug.set(self._id, 'off')
|
||||||
self._id,
|
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'off')
|
||||||
self.status_off)
|
verisure.update_smartplug()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
verisure.update()
|
verisure.update_smartplug()
|
||||||
|
@ -9,11 +9,14 @@ https://home-assistant.io/components/switch.wemo/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_WEMO_SUBSCRIPTION_REGISTRY = None
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument, too-many-function-args
|
# pylint: disable=unused-argument, too-many-function-args
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
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
|
||||||
import pywemo.discovery as discovery
|
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:
|
if discovery_info is not None:
|
||||||
location = discovery_info[2]
|
location = discovery_info[2]
|
||||||
mac = discovery_info[3]
|
mac = discovery_info[3]
|
||||||
@ -47,6 +62,23 @@ class WemoSwitch(SwitchDevice):
|
|||||||
self.insight_params = None
|
self.insight_params = None
|
||||||
self.maker_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
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
""" Returns the id of this WeMo switch """
|
""" Returns the id of this WeMo switch """
|
||||||
|
209
homeassistant/components/tellduslive.py
Normal file
209
homeassistant/components/tellduslive.py
Normal file
@ -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/
|
https://home-assistant.io/components/verisure/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
@ -26,15 +28,14 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
|||||||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||||
|
|
||||||
DEPENDENCIES = ['alarm_control_panel']
|
DEPENDENCIES = ['alarm_control_panel']
|
||||||
REQUIREMENTS = [
|
REQUIREMENTS = ['vsure==0.4.3']
|
||||||
'https://github.com/persandstrom/python-verisure/archive/'
|
|
||||||
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
|
|
||||||
]
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MY_PAGES = None
|
MY_PAGES = None
|
||||||
STATUS = {}
|
ALARM_STATUS = {}
|
||||||
|
SMARTPLUG_STATUS = {}
|
||||||
|
CLIMATE_STATUS = {}
|
||||||
|
|
||||||
VERISURE_LOGIN_ERROR = None
|
VERISURE_LOGIN_ERROR = None
|
||||||
VERISURE_ERROR = None
|
VERISURE_ERROR = None
|
||||||
@ -47,7 +48,7 @@ SHOW_SMARTPLUGS = True
|
|||||||
# if wrong password was given don't try again
|
# if wrong password was given don't try again
|
||||||
WRONG_PASSWORD_GIVEN = False
|
WRONG_PASSWORD_GIVEN = False
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=1)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
@ -60,10 +61,6 @@ def setup(hass, config):
|
|||||||
|
|
||||||
from verisure import MyPages, LoginError, Error
|
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
|
global SHOW_THERMOMETERS, SHOW_HYGROMETERS, SHOW_ALARM, SHOW_SMARTPLUGS
|
||||||
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
||||||
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '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)
|
_LOGGER.error('Could not log in to verisure mypages, %s', ex)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
update()
|
update_alarm()
|
||||||
|
update_climate()
|
||||||
|
update_smartplug()
|
||||||
|
|
||||||
# Load components for the devices in the ISY controller that we support
|
# Load components for the devices in the ISY controller that we support
|
||||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||||
@ -101,24 +100,10 @@ def setup(hass, config):
|
|||||||
return True
|
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():
|
def reconnect():
|
||||||
""" Reconnect to verisure mypages. """
|
""" Reconnect to verisure mypages. """
|
||||||
try:
|
try:
|
||||||
|
time.sleep(1)
|
||||||
MY_PAGES.login()
|
MY_PAGES.login()
|
||||||
except VERISURE_LOGIN_ERROR as ex:
|
except VERISURE_LOGIN_ERROR as ex:
|
||||||
_LOGGER.error("Could not login to Verisure mypages, %s", ex)
|
_LOGGER.error("Could not login to Verisure mypages, %s", ex)
|
||||||
@ -129,19 +114,31 @@ def reconnect():
|
|||||||
|
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
@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. """
|
""" Updates the status of verisure components. """
|
||||||
if WRONG_PASSWORD_GIVEN:
|
if WRONG_PASSWORD_GIVEN:
|
||||||
_LOGGER.error('Wrong password')
|
_LOGGER.error('Wrong password')
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM):
|
for overview in get_function():
|
||||||
STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview
|
status[overview.id] = overview
|
||||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE):
|
except (ConnectionError, VERISURE_ERROR) as ex:
|
||||||
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:
|
|
||||||
_LOGGER.error('Caught connection error %s, tries to reconnect', ex)
|
_LOGGER.error('Caught connection error %s, tries to reconnect', ex)
|
||||||
reconnect()
|
reconnect()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant
|
Core components of Home Assistant.
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Home Assistant is a Home Automation framework for observing the state
|
Home Assistant is a Home Automation framework for observing the state
|
||||||
of entities and react to changes.
|
of entities and react to changes.
|
||||||
@ -53,9 +52,10 @@ _MockHA = namedtuple("MockHomeAssistant", ['bus'])
|
|||||||
|
|
||||||
|
|
||||||
class HomeAssistant(object):
|
class HomeAssistant(object):
|
||||||
""" Core class to route all communication to right components. """
|
"""Root object of the Home Assistant home automation."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Initialize new Home Assistant object."""
|
||||||
self.pool = pool = create_worker_pool()
|
self.pool = pool = create_worker_pool()
|
||||||
self.bus = EventBus(pool)
|
self.bus = EventBus(pool)
|
||||||
self.services = ServiceRegistry(self.bus, pool)
|
self.services = ServiceRegistry(self.bus, pool)
|
||||||
@ -63,7 +63,7 @@ class HomeAssistant(object):
|
|||||||
self.config = Config()
|
self.config = Config()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Start home assistant. """
|
"""Start home assistant."""
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||||
|
|
||||||
@ -71,12 +71,11 @@ class HomeAssistant(object):
|
|||||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
|
||||||
def block_till_stopped(self):
|
def block_till_stopped(self):
|
||||||
""" Will register service homeassistant/stop and
|
"""Register service homeassistant/stop and will block until called."""
|
||||||
will block until called. """
|
|
||||||
request_shutdown = threading.Event()
|
request_shutdown = threading.Event()
|
||||||
|
|
||||||
def stop_homeassistant(*args):
|
def stop_homeassistant(*args):
|
||||||
""" Stops Home Assistant. """
|
"""Stop Home Assistant."""
|
||||||
request_shutdown.set()
|
request_shutdown.set()
|
||||||
|
|
||||||
self.services.register(
|
self.services.register(
|
||||||
@ -98,7 +97,7 @@ class HomeAssistant(object):
|
|||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stops Home Assistant and shuts down all threads. """
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
_LOGGER.info("Stopping")
|
_LOGGER.info("Stopping")
|
||||||
|
|
||||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
@ -150,8 +149,7 @@ class HomeAssistant(object):
|
|||||||
|
|
||||||
|
|
||||||
class JobPriority(util.OrderedEnum):
|
class JobPriority(util.OrderedEnum):
|
||||||
""" Provides priorities for bus events. """
|
"""Provides job priorities for event bus jobs."""
|
||||||
# pylint: disable=no-init,too-few-public-methods
|
|
||||||
|
|
||||||
EVENT_CALLBACK = 0
|
EVENT_CALLBACK = 0
|
||||||
EVENT_SERVICE = 1
|
EVENT_SERVICE = 1
|
||||||
@ -161,7 +159,7 @@ class JobPriority(util.OrderedEnum):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_event_type(event_type):
|
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:
|
if event_type == EVENT_TIME_CHANGED:
|
||||||
return JobPriority.EVENT_TIME
|
return JobPriority.EVENT_TIME
|
||||||
elif event_type == EVENT_STATE_CHANGED:
|
elif event_type == EVENT_STATE_CHANGED:
|
||||||
@ -175,8 +173,7 @@ class JobPriority(util.OrderedEnum):
|
|||||||
|
|
||||||
|
|
||||||
class EventOrigin(enum.Enum):
|
class EventOrigin(enum.Enum):
|
||||||
""" Distinguish between origin of event. """
|
"""Represents origin of an event."""
|
||||||
# pylint: disable=no-init,too-few-public-methods
|
|
||||||
|
|
||||||
local = "LOCAL"
|
local = "LOCAL"
|
||||||
remote = "REMOTE"
|
remote = "REMOTE"
|
||||||
@ -185,14 +182,15 @@ class EventOrigin(enum.Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
class Event(object):
|
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']
|
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
|
||||||
|
|
||||||
def __init__(self, event_type, data=None, origin=EventOrigin.local,
|
def __init__(self, event_type, data=None, origin=EventOrigin.local,
|
||||||
time_fired=None):
|
time_fired=None):
|
||||||
|
"""Initialize a new event."""
|
||||||
self.event_type = event_type
|
self.event_type = event_type
|
||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
@ -200,7 +198,7 @@ class Event(object):
|
|||||||
time_fired or dt_util.utcnow())
|
time_fired or dt_util.utcnow())
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Returns a dict representation of this Event. """
|
"""Create a dict representation of this Event."""
|
||||||
return {
|
return {
|
||||||
'event_type': self.event_type,
|
'event_type': self.event_type,
|
||||||
'data': dict(self.data),
|
'data': dict(self.data),
|
||||||
@ -227,26 +225,23 @@ class Event(object):
|
|||||||
|
|
||||||
|
|
||||||
class EventBus(object):
|
class EventBus(object):
|
||||||
""" Class that allows different components to communicate via services
|
"""Allows firing of and listening for events."""
|
||||||
and events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pool=None):
|
def __init__(self, pool=None):
|
||||||
|
"""Initialize a new event bus."""
|
||||||
self._listeners = {}
|
self._listeners = {}
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._pool = pool or create_worker_pool()
|
self._pool = pool or create_worker_pool()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listeners(self):
|
def listeners(self):
|
||||||
""" Dict with events that is being listened for and the number
|
"""Dict with events and the number of listeners."""
|
||||||
of listeners.
|
|
||||||
"""
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return {key: len(self._listeners[key])
|
return {key: len(self._listeners[key])
|
||||||
for key in self._listeners}
|
for key in self._listeners}
|
||||||
|
|
||||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
||||||
""" Fire an event. """
|
"""Fire an event."""
|
||||||
if not self._pool.running:
|
if not self._pool.running:
|
||||||
raise HomeAssistantError('Home Assistant has shut down.')
|
raise HomeAssistantError('Home Assistant has shut down.')
|
||||||
|
|
||||||
@ -271,7 +266,7 @@ class EventBus(object):
|
|||||||
self._pool.add_job(job_priority, (func, event))
|
self._pool.add_job(job_priority, (func, event))
|
||||||
|
|
||||||
def listen(self, event_type, listener):
|
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``
|
To listen to all events specify the constant ``MATCH_ALL``
|
||||||
as event_type.
|
as event_type.
|
||||||
@ -283,7 +278,7 @@ class EventBus(object):
|
|||||||
self._listeners[event_type] = [listener]
|
self._listeners[event_type] = [listener]
|
||||||
|
|
||||||
def listen_once(self, 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``
|
To listen to all events specify the constant ``MATCH_ALL``
|
||||||
as event_type.
|
as event_type.
|
||||||
@ -292,7 +287,7 @@ class EventBus(object):
|
|||||||
"""
|
"""
|
||||||
@ft.wraps(listener)
|
@ft.wraps(listener)
|
||||||
def onetime_listener(event):
|
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'):
|
if hasattr(onetime_listener, 'run'):
|
||||||
return
|
return
|
||||||
# Set variable so that we will never run twice.
|
# Set variable so that we will never run twice.
|
||||||
@ -311,7 +306,7 @@ class EventBus(object):
|
|||||||
return onetime_listener
|
return onetime_listener
|
||||||
|
|
||||||
def remove_listener(self, event_type, 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:
|
with self._lock:
|
||||||
try:
|
try:
|
||||||
self._listeners[event_type].remove(listener)
|
self._listeners[event_type].remove(listener)
|
||||||
@ -343,6 +338,7 @@ class State(object):
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
||||||
last_updated=None):
|
last_updated=None):
|
||||||
|
"""Initialize a new state."""
|
||||||
if not ENTITY_ID_PATTERN.match(entity_id):
|
if not ENTITY_ID_PATTERN.match(entity_id):
|
||||||
raise InvalidEntityFormatError((
|
raise InvalidEntityFormatError((
|
||||||
"Invalid entity id encountered: {}. "
|
"Invalid entity id encountered: {}. "
|
||||||
@ -363,31 +359,33 @@ class State(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
""" Returns domain of this state. """
|
"""Domain of this state."""
|
||||||
return util.split_entity_id(self.entity_id)[0]
|
return util.split_entity_id(self.entity_id)[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def object_id(self):
|
def object_id(self):
|
||||||
""" Returns object_id of this state. """
|
"""Object id of this state."""
|
||||||
return util.split_entity_id(self.entity_id)[1]
|
return util.split_entity_id(self.entity_id)[1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" Name to represent this state. """
|
"""Name of this state."""
|
||||||
return (
|
return (
|
||||||
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
||||||
self.object_id.replace('_', ' '))
|
self.object_id.replace('_', ' '))
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
""" Creates a copy of itself. """
|
"""Return a copy of the state."""
|
||||||
return State(self.entity_id, self.state,
|
return State(self.entity_id, self.state,
|
||||||
dict(self.attributes), self.last_changed,
|
dict(self.attributes), self.last_changed,
|
||||||
self.last_updated)
|
self.last_updated)
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Converts State to a dict to be used within JSON.
|
"""Return a dict representation of the State.
|
||||||
Ensures: state == State.from_dict(state.as_dict()) """
|
|
||||||
|
|
||||||
|
To be used for JSON serialization.
|
||||||
|
Ensures: state == State.from_dict(state.as_dict())
|
||||||
|
"""
|
||||||
return {'entity_id': self.entity_id,
|
return {'entity_id': self.entity_id,
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
'attributes': self.attributes,
|
'attributes': self.attributes,
|
||||||
@ -396,11 +394,11 @@ class State(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, json_dict):
|
def from_dict(cls, json_dict):
|
||||||
""" Static method to create a state from a dict.
|
"""Initialize a state from a dict.
|
||||||
Ensures: state == State.from_json_dict(state.to_json_dict()) """
|
|
||||||
|
|
||||||
if not (json_dict and
|
Ensures: state == State.from_json_dict(state.to_json_dict())
|
||||||
'entity_id' in json_dict and
|
"""
|
||||||
|
if not (json_dict and 'entity_id' in json_dict and
|
||||||
'state' in json_dict):
|
'state' in json_dict):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -433,15 +431,16 @@ class State(object):
|
|||||||
|
|
||||||
|
|
||||||
class StateMachine(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):
|
def __init__(self, bus):
|
||||||
|
"""Initialize state machine."""
|
||||||
self._states = {}
|
self._states = {}
|
||||||
self._bus = bus
|
self._bus = bus
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
def entity_ids(self, domain_filter=None):
|
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:
|
if domain_filter is None:
|
||||||
return list(self._states.keys())
|
return list(self._states.keys())
|
||||||
|
|
||||||
@ -451,35 +450,36 @@ class StateMachine(object):
|
|||||||
if state.domain == domain_filter]
|
if state.domain == domain_filter]
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
""" Returns a list of all states. """
|
"""Create a list of all states."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return [state.copy() for state in self._states.values()]
|
return [state.copy() for state in self._states.values()]
|
||||||
|
|
||||||
def get(self, entity_id):
|
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())
|
state = self._states.get(entity_id.lower())
|
||||||
|
|
||||||
# Make a copy so people won't mutate the state
|
# Make a copy so people won't mutate the state
|
||||||
return state.copy() if state else None
|
return state.copy() if state else None
|
||||||
|
|
||||||
def is_state(self, entity_id, state):
|
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()
|
entity_id = entity_id.lower()
|
||||||
|
|
||||||
return (entity_id in self._states and
|
return (entity_id in self._states and
|
||||||
self._states[entity_id].state == state)
|
self._states[entity_id].state == state)
|
||||||
|
|
||||||
def remove(self, entity_id):
|
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()
|
entity_id = entity_id.lower()
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return self._states.pop(entity_id, None) is not None
|
return self._states.pop(entity_id, None) is not None
|
||||||
|
|
||||||
def set(self, entity_id, new_state, attributes=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.
|
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)
|
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||||
|
|
||||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
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(
|
_LOGGER.warning(
|
||||||
'hass.states.track_change is deprecated. '
|
'hass.states.track_change is deprecated. '
|
||||||
'Use homeassistant.helpers.event.track_state_change instead.')
|
'Use homeassistant.helpers.event.track_state_change instead.')
|
||||||
@ -527,33 +525,36 @@ class StateMachine(object):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class Service(object):
|
class Service(object):
|
||||||
""" Represents a service. """
|
"""Represents a callable service."""
|
||||||
|
|
||||||
__slots__ = ['func', 'description', 'fields']
|
__slots__ = ['func', 'description', 'fields']
|
||||||
|
|
||||||
def __init__(self, func, description, fields):
|
def __init__(self, func, description, fields):
|
||||||
|
"""Initialize a service."""
|
||||||
self.func = func
|
self.func = func
|
||||||
self.description = description or ''
|
self.description = description or ''
|
||||||
self.fields = fields or {}
|
self.fields = fields or {}
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Return dictionary representation of this service. """
|
"""Return dictionary representation of this service."""
|
||||||
return {
|
return {
|
||||||
'description': self.description,
|
'description': self.description,
|
||||||
'fields': self.fields,
|
'fields': self.fields,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __call__(self, call):
|
def __call__(self, call):
|
||||||
|
"""Execute the service."""
|
||||||
self.func(call)
|
self.func(call)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class ServiceCall(object):
|
class ServiceCall(object):
|
||||||
""" Represents a call to a service. """
|
"""Represents a call to a service."""
|
||||||
|
|
||||||
__slots__ = ['domain', 'service', 'data']
|
__slots__ = ['domain', 'service', 'data']
|
||||||
|
|
||||||
def __init__(self, domain, service, data=None):
|
def __init__(self, domain, service, data=None):
|
||||||
|
"""Initialize a service call."""
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.service = service
|
self.service = service
|
||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
@ -567,9 +568,10 @@ class ServiceCall(object):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceRegistry(object):
|
class ServiceRegistry(object):
|
||||||
""" Offers services over the eventbus. """
|
"""Offers services over the eventbus."""
|
||||||
|
|
||||||
def __init__(self, bus, pool=None):
|
def __init__(self, bus, pool=None):
|
||||||
|
"""Initialize a service registry."""
|
||||||
self._services = {}
|
self._services = {}
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._pool = pool or create_worker_pool()
|
self._pool = pool or create_worker_pool()
|
||||||
@ -579,14 +581,14 @@ class ServiceRegistry(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self):
|
def services(self):
|
||||||
""" Dict with per domain a list of available services. """
|
"""Dict with per domain a list of available services."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return {domain: {key: value.as_dict() for key, value
|
return {domain: {key: value.as_dict() for key, value
|
||||||
in self._services[domain].items()}
|
in self._services[domain].items()}
|
||||||
for domain in self._services}
|
for domain in self._services}
|
||||||
|
|
||||||
def has_service(self, domain, service):
|
def has_service(self, domain, service):
|
||||||
""" Returns True if specified service exists. """
|
"""Test if specified service exists."""
|
||||||
return service in self._services.get(domain, [])
|
return service in self._services.get(domain, [])
|
||||||
|
|
||||||
def register(self, domain, service, service_func, description=None):
|
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):
|
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.
|
Specify blocking=True to wait till service is executed.
|
||||||
Waits a maximum of SERVICE_CALL_LIMIT.
|
Waits a maximum of SERVICE_CALL_LIMIT.
|
||||||
|
|
||||||
@ -635,10 +638,7 @@ class ServiceRegistry(object):
|
|||||||
executed_event = threading.Event()
|
executed_event = threading.Event()
|
||||||
|
|
||||||
def service_executed(call):
|
def service_executed(call):
|
||||||
"""
|
"""Callback method that is called when service is executed."""
|
||||||
Called when a service is executed.
|
|
||||||
Will set the event if matches our service call.
|
|
||||||
"""
|
|
||||||
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
||||||
executed_event.set()
|
executed_event.set()
|
||||||
|
|
||||||
@ -653,7 +653,7 @@ class ServiceRegistry(object):
|
|||||||
return success
|
return success
|
||||||
|
|
||||||
def _event_to_service_call(self, event):
|
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)
|
service_data = dict(event.data)
|
||||||
domain = service_data.pop(ATTR_DOMAIN, None)
|
domain = service_data.pop(ATTR_DOMAIN, None)
|
||||||
service = service_data.pop(ATTR_SERVICE, None)
|
service = service_data.pop(ATTR_SERVICE, None)
|
||||||
@ -670,7 +670,7 @@ class ServiceRegistry(object):
|
|||||||
(service_handler, service_call)))
|
(service_handler, service_call)))
|
||||||
|
|
||||||
def _execute_service(self, service_and_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 = service_and_call
|
||||||
service(call)
|
service(call)
|
||||||
|
|
||||||
@ -680,16 +680,17 @@ class ServiceRegistry(object):
|
|||||||
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
|
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
|
||||||
|
|
||||||
def _generate_unique_id(self):
|
def _generate_unique_id(self):
|
||||||
""" Generates a unique service call id. """
|
"""Generate a unique service call id."""
|
||||||
self._cur_id += 1
|
self._cur_id += 1
|
||||||
return "{}-{}".format(id(self), self._cur_id)
|
return "{}-{}".format(id(self), self._cur_id)
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
""" Configuration settings for Home Assistant. """
|
"""Configuration settings for Home Assistant."""
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Initialize a new config object."""
|
||||||
self.latitude = None
|
self.latitude = None
|
||||||
self.longitude = None
|
self.longitude = None
|
||||||
self.temperature_unit = None
|
self.temperature_unit = None
|
||||||
@ -709,15 +710,15 @@ class Config(object):
|
|||||||
self.config_dir = get_default_config_dir()
|
self.config_dir = get_default_config_dir()
|
||||||
|
|
||||||
def distance(self, lat, lon):
|
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)
|
return location.distance(self.latitude, self.longitude, lat, lon)
|
||||||
|
|
||||||
def path(self, *path):
|
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)
|
return os.path.join(self.config_dir, *path)
|
||||||
|
|
||||||
def temperature(self, value, unit):
|
def temperature(self, value, unit):
|
||||||
""" Converts temperature to user preferred unit if set. """
|
"""Convert temperature to user preferred unit if set."""
|
||||||
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
|
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
|
||||||
self.temperature_unit and unit != self.temperature_unit):
|
self.temperature_unit and unit != self.temperature_unit):
|
||||||
return value, unit
|
return value, unit
|
||||||
@ -732,7 +733,7 @@ class Config(object):
|
|||||||
self.temperature_unit)
|
self.temperature_unit)
|
||||||
|
|
||||||
def as_dict(self):
|
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
|
time_zone = self.time_zone or dt_util.UTC
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -747,7 +748,7 @@ class Config(object):
|
|||||||
|
|
||||||
|
|
||||||
def create_timer(hass, interval=TIMER_INTERVAL):
|
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 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
|
# We want this so other modules can use that to make sure they fire
|
||||||
# every minute.
|
# every minute.
|
||||||
@ -810,12 +811,12 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
|||||||
|
|
||||||
|
|
||||||
def create_worker_pool(worker_count=None):
|
def create_worker_pool(worker_count=None):
|
||||||
""" Creates a worker pool to be used. """
|
"""Create a worker pool."""
|
||||||
if worker_count is None:
|
if worker_count is None:
|
||||||
worker_count = MIN_WORKER_THREAD
|
worker_count = MIN_WORKER_THREAD
|
||||||
|
|
||||||
def job_handler(job):
|
def job_handler(job):
|
||||||
""" Called whenever a job is available to do. """
|
"""Called whenever a job is available to do."""
|
||||||
try:
|
try:
|
||||||
func, arg = job
|
func, arg = job
|
||||||
func(arg)
|
func(arg)
|
||||||
@ -825,8 +826,7 @@ def create_worker_pool(worker_count=None):
|
|||||||
_LOGGER.exception("BusHandler:Exception doing job")
|
_LOGGER.exception("BusHandler:Exception doing job")
|
||||||
|
|
||||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
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(
|
_LOGGER.warning(
|
||||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
"WorkerPool:All %d threads are busy and %d jobs pending",
|
||||||
worker_count, pending_jobs_count)
|
worker_count, pending_jobs_count)
|
||||||
|
@ -53,8 +53,12 @@ def color_xy_brightness_to_RGB(vX, vY, brightness):
|
|||||||
return (0, 0, 0)
|
return (0, 0, 0)
|
||||||
|
|
||||||
Y = brightness
|
Y = brightness
|
||||||
X = (Y / vY) * vX
|
if vY != 0:
|
||||||
Z = (Y / vY) * (1 - vX - vY)
|
X = (Y / vY) * vX
|
||||||
|
Z = (Y / vY) * (1 - vX - vY)
|
||||||
|
else:
|
||||||
|
X = 0
|
||||||
|
Z = 0
|
||||||
|
|
||||||
# Convert to RGB using Wide RGB D65 conversion.
|
# Convert to RGB using Wide RGB D65 conversion.
|
||||||
r = X * 1.612 - Y * 0.203 - Z * 0.302
|
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)
|
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.
|
""" Converts a string to a UTC datetime object.
|
||||||
|
|
||||||
@rtype: datetime
|
@rtype: datetime
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return dt.datetime.strptime(
|
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
|
except ValueError: # If dt_str did not match our format
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import collections
|
|||||||
import requests
|
import requests
|
||||||
from vincenty import vincenty
|
from vincenty import vincenty
|
||||||
|
|
||||||
|
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
||||||
|
|
||||||
|
|
||||||
LocationInfo = collections.namedtuple(
|
LocationInfo = collections.namedtuple(
|
||||||
"LocationInfo",
|
"LocationInfo",
|
||||||
@ -34,3 +36,20 @@ def detect_location_info():
|
|||||||
def distance(lat1, lon1, lat2, lon2):
|
def distance(lat1, lon1, lat2, lon2):
|
||||||
""" Calculate the distance in meters between two points. """
|
""" Calculate the distance in meters between two points. """
|
||||||
return vincenty((lat1, lon1), (lat2, lon2)) * 1000
|
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
|
xmltodict
|
||||||
|
|
||||||
# homeassistant.components.sun
|
# homeassistant.components.sun
|
||||||
# homeassistant.components.sensor.yr
|
|
||||||
astral==0.8.1
|
astral==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.switch.edimax
|
# homeassistant.components.switch.edimax
|
||||||
@ -174,7 +173,10 @@ hikvision==0.4
|
|||||||
orvibo==1.1.0
|
orvibo==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.switch.wemo
|
# homeassistant.components.switch.wemo
|
||||||
pywemo==0.3.3
|
pywemo==0.3.4
|
||||||
|
|
||||||
|
# homeassistant.components.tellduslive
|
||||||
|
tellive-py==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.thermostat.heatmiser
|
# homeassistant.components.thermostat.heatmiser
|
||||||
heatmiserV3==0.9.1
|
heatmiserV3==0.9.1
|
||||||
@ -189,7 +191,7 @@ python-nest==2.6.0
|
|||||||
radiotherm==1.2
|
radiotherm==1.2
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
# homeassistant.components.verisure
|
||||||
https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6
|
vsure==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
6
requirements_test.txt
Normal file
6
requirements_test.txt
Normal file
@ -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=$?
|
REQ_STATUS=$?
|
||||||
|
|
||||||
echo "Installing development dependencies.."
|
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=$?
|
REQ_DEV_STATUS=$?
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" != "3.4" ]; then
|
if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then
|
||||||
NO_LINT=1
|
NO_LINT=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -115,7 +115,6 @@ def main():
|
|||||||
|
|
||||||
if sys.argv[-1] == 'validate':
|
if sys.argv[-1] == 'validate':
|
||||||
if validate_file(data):
|
if validate_file(data):
|
||||||
print("requirements_all.txt is up to date.")
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
print("******* ERROR")
|
print("******* ERROR")
|
||||||
print("requirements_all.txt is not up to date")
|
print("requirements_all.txt is not up to date")
|
||||||
|
@ -3,13 +3,16 @@
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
echo "Checking style with flake8..."
|
echo "Checking style with flake8..."
|
||||||
|
tput setaf 1
|
||||||
flake8 --exclude www_static homeassistant
|
flake8 --exclude www_static homeassistant
|
||||||
|
|
||||||
FLAKE8_STATUS=$?
|
FLAKE8_STATUS=$?
|
||||||
|
tput sgr0
|
||||||
|
|
||||||
echo "Checking style with pylint..."
|
echo "Checking style with pylint..."
|
||||||
|
tput setaf 1
|
||||||
pylint homeassistant
|
pylint homeassistant
|
||||||
PYLINT_STATUS=$?
|
PYLINT_STATUS=$?
|
||||||
|
tput sgr0
|
||||||
|
|
||||||
if [ $FLAKE8_STATUS -eq 0 ]
|
if [ $FLAKE8_STATUS -eq 0 ]
|
||||||
then
|
then
|
||||||
|
8
setup.cfg
Normal file
8
setup.cfg
Normal file
@ -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',
|
'pytz>=2015.4',
|
||||||
'pip>=7.0.0',
|
'pip>=7.0.0',
|
||||||
'vincenty==0.1.3',
|
'vincenty==0.1.3',
|
||||||
'jinja2>=2.8'
|
'jinja2>=2.8',
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@ -33,6 +33,7 @@ setup(
|
|||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms='any',
|
platforms='any',
|
||||||
install_requires=REQUIRES,
|
install_requires=REQUIRES,
|
||||||
|
test_suite='tests',
|
||||||
keywords=['home', 'automation'],
|
keywords=['home', 'automation'],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
@ -46,5 +47,5 @@ setup(
|
|||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.4',
|
||||||
'Topic :: Home Automation'
|
'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)
|
fire_time_changed(self.hass, trigger_time)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
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.
|
Tests Yr sensor.
|
||||||
"""
|
"""
|
||||||
import unittest
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.sensor as sensor
|
import homeassistant.components.sensor as sensor
|
||||||
|
|
||||||
|
|
||||||
class TestSensorYr(unittest.TestCase):
|
@pytest.mark.usefixtures('betamax_session')
|
||||||
|
class TestSensorYr:
|
||||||
""" Test the Yr sensor. """
|
""" Test the Yr sensor. """
|
||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
def setup_method(self, method):
|
||||||
self.hass = ha.HomeAssistant()
|
self.hass = ha.HomeAssistant()
|
||||||
latitude = 32.87336
|
self.hass.config.latitude = 32.87336
|
||||||
longitude = 117.22743
|
self.hass.config.longitude = 117.22743
|
||||||
|
|
||||||
# Compare it with the real data
|
def teardown_method(self, method):
|
||||||
self.hass.config.latitude = latitude
|
|
||||||
self.hass.config.longitude = longitude
|
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
|
||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_default_setup(self):
|
def test_default_setup(self, betamax_session):
|
||||||
self.assertTrue(sensor.setup(self.hass, {
|
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||||
'sensor': {
|
return_value=betamax_session):
|
||||||
'platform': 'yr',
|
assert sensor.setup(self.hass, {
|
||||||
}
|
'sensor': {
|
||||||
}))
|
'platform': 'yr',
|
||||||
|
'elevation': 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_symbol')
|
state = self.hass.states.get('sensor.yr_symbol')
|
||||||
|
|
||||||
self.assertTrue(state.state.isnumeric())
|
assert state.state.isnumeric()
|
||||||
self.assertEqual(None,
|
assert state.attributes.get('unit_of_measurement') is None
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
|
||||||
def test_custom_setup(self):
|
def test_custom_setup(self, betamax_session):
|
||||||
self.assertTrue(sensor.setup(self.hass, {
|
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||||
'sensor': {
|
return_value=betamax_session):
|
||||||
'platform': 'yr',
|
assert sensor.setup(self.hass, {
|
||||||
'monitored_conditions': {'pressure', 'windDirection', 'humidity', 'fog', 'windSpeed'}
|
'sensor': {
|
||||||
}
|
'platform': 'yr',
|
||||||
}))
|
'elevation': 0,
|
||||||
state = self.hass.states.get('sensor.yr_symbol')
|
'monitored_conditions': {
|
||||||
self.assertEqual(None, state)
|
'pressure',
|
||||||
|
'windDirection',
|
||||||
|
'humidity',
|
||||||
|
'fog',
|
||||||
|
'windSpeed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_pressure')
|
state = self.hass.states.get('sensor.yr_pressure')
|
||||||
self.assertEqual('hPa',
|
assert 'hPa', state.attributes.get('unit_of_measurement')
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_wind_direction')
|
state = self.hass.states.get('sensor.yr_wind_direction')
|
||||||
self.assertEqual('°',
|
assert '°', state.attributes.get('unit_of_measurement')
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_humidity')
|
state = self.hass.states.get('sensor.yr_humidity')
|
||||||
self.assertEqual('%',
|
assert '%', state.attributes.get('unit_of_measurement')
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_fog')
|
state = self.hass.states.get('sensor.yr_fog')
|
||||||
self.assertEqual('%',
|
assert '%', state.attributes.get('unit_of_measurement')
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_wind_speed')
|
state = self.hass.states.get('sensor.yr_wind_speed')
|
||||||
self.assertEqual('m/s',
|
assert 'm/s', state.attributes.get('unit_of_measurement')
|
||||||
state.attributes.get('unit_of_measurement'))
|
|
||||||
|
@ -5,11 +5,10 @@ tests.test_component_history
|
|||||||
Tests the history component.
|
Tests the history component.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import time
|
from datetime import timedelta
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -25,21 +24,24 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
def setUp(self): # pylint: disable=invalid-name
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
""" Init needed objects. """
|
""" Init needed objects. """
|
||||||
self.hass = get_test_home_assistant(1)
|
self.hass = get_test_home_assistant(1)
|
||||||
self.init_rec = False
|
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
if self.init_rec:
|
db_path = self.hass.config.path(recorder.DB_FILE)
|
||||||
recorder._INSTANCE.block_till_done()
|
if os.path.isfile(db_path):
|
||||||
os.remove(self.hass.config.path(recorder.DB_FILE))
|
os.remove(db_path)
|
||||||
|
|
||||||
def init_recorder(self):
|
def init_recorder(self):
|
||||||
recorder.setup(self.hass, {})
|
recorder.setup(self.hass, {})
|
||||||
self.hass.start()
|
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()
|
recorder._INSTANCE.block_till_done()
|
||||||
self.init_rec = True
|
|
||||||
|
|
||||||
def test_setup(self):
|
def test_setup(self):
|
||||||
""" Test setup method of history. """
|
""" Test setup method of history. """
|
||||||
@ -56,12 +58,11 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
for i in range(7):
|
for i in range(7):
|
||||||
self.hass.states.set(entity_id, "State {}".format(i))
|
self.hass.states.set(entity_id, "State {}".format(i))
|
||||||
|
|
||||||
|
self.wait_recording_done()
|
||||||
|
|
||||||
if i > 1:
|
if i > 1:
|
||||||
states.append(self.hass.states.get(entity_id))
|
states.append(self.hass.states.get(entity_id))
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
recorder._INSTANCE.block_till_done()
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
list(reversed(states)), history.last_5_states(entity_id))
|
list(reversed(states)), history.last_5_states(entity_id))
|
||||||
|
|
||||||
@ -70,22 +71,9 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
self.init_recorder()
|
self.init_recorder()
|
||||||
states = []
|
states = []
|
||||||
|
|
||||||
for i in range(5):
|
now = dt_util.utcnow()
|
||||||
state = ha.State(
|
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||||
'test.point_in_time_{}'.format(i % 5),
|
return_value=now):
|
||||||
"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):
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
state = ha.State(
|
state = ha.State(
|
||||||
'test.point_in_time_{}'.format(i % 5),
|
'test.point_in_time_{}'.format(i % 5),
|
||||||
@ -93,16 +81,32 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
{'attribute_test': i})
|
{'attribute_test': i})
|
||||||
|
|
||||||
mock_state_change_event(self.hass, state)
|
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
|
# Get states returns everything before POINT
|
||||||
self.assertEqual(states,
|
self.assertEqual(states,
|
||||||
sorted(history.get_states(point),
|
sorted(history.get_states(future),
|
||||||
key=lambda state: state.entity_id))
|
key=lambda state: state.entity_id))
|
||||||
|
|
||||||
# Test get_state here because we have a DB setup
|
# Test get_state here because we have a DB setup
|
||||||
self.assertEqual(
|
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):
|
def test_state_changes_during_period(self):
|
||||||
self.init_recorder()
|
self.init_recorder()
|
||||||
@ -110,19 +114,20 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
|
|
||||||
def set_state(state):
|
def set_state(state):
|
||||||
self.hass.states.set(entity_id, state)
|
self.hass.states.set(entity_id, state)
|
||||||
self.hass.pool.block_till_done()
|
self.wait_recording_done()
|
||||||
recorder._INSTANCE.block_till_done()
|
|
||||||
|
|
||||||
return self.hass.states.get(entity_id)
|
return self.hass.states.get(entity_id)
|
||||||
|
|
||||||
set_state('idle')
|
|
||||||
set_state('YouTube')
|
|
||||||
|
|
||||||
start = dt_util.utcnow()
|
start = dt_util.utcnow()
|
||||||
point = start + timedelta(seconds=1)
|
point = start + timedelta(seconds=1)
|
||||||
end = point + 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 = [
|
states = [
|
||||||
set_state('idle'),
|
set_state('idle'),
|
||||||
set_state('Netflix'),
|
set_state('Netflix'),
|
||||||
@ -130,10 +135,11 @@ class TestComponentHistory(unittest.TestCase):
|
|||||||
set_state('YouTube'),
|
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('Netflix')
|
||||||
set_state('Plex')
|
set_state('Plex')
|
||||||
|
|
||||||
self.assertEqual(
|
hist = history.state_changes_during_period(start, end, entity_id)
|
||||||
{entity_id: states},
|
|
||||||
history.state_changes_during_period(start, end, entity_id))
|
self.assertEqual(states, hist[entity_id])
|
||||||
|
@ -32,6 +32,60 @@ class TestScene(unittest.TestCase):
|
|||||||
'scene': [[]]
|
'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):
|
def test_activate_scene(self):
|
||||||
test_light = loader.get_component('light.test')
|
test_light = loader.get_component('light.test')
|
||||||
test_light.init()
|
test_light.init()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user