mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Merge branch 'dev' of https://github.com/balloob/home-assistant into dev
This commit is contained in:
commit
cbd3860585
@ -15,6 +15,10 @@ omit =
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/*/tellstick.py
|
||||
|
||||
homeassistant/components/tellduslive.py
|
||||
homeassistant/components/*/tellduslive.py
|
||||
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
@ -41,7 +45,6 @@ omit =
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/geofancy.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
homeassistant/components/device_tracker/luci.py
|
||||
homeassistant/components/device_tracker/netgear.py
|
||||
@ -98,6 +101,7 @@ omit =
|
||||
homeassistant/components/sensor/systemmonitor.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
homeassistant/components/sensor/time_date.py
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/worldclock.py
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -41,6 +41,7 @@ Icon
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
.eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
|
@ -8,8 +8,8 @@ python:
|
||||
- 3.4
|
||||
- 3.5
|
||||
install:
|
||||
# Validate requirements_all.txt on Python 3.5
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then python3 setup.py develop; script/gen_requirements_all.py validate; fi
|
||||
# Validate requirements_all.txt on Python 3.4
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python3 setup.py -q develop 2>/dev/null; tput setaf 1; script/gen_requirements_all.py validate; tput sgr0; fi
|
||||
- script/bootstrap_server
|
||||
script:
|
||||
- script/cibuild
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Paulus Schoutsen
|
||||
Copyright (c) 2016 Paulus Schoutsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -275,7 +275,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
|
||||
datefmt='%y-%m-%d %H:%M:%S'))
|
||||
logger = logging.getLogger('')
|
||||
logger.addHandler(err_handler)
|
||||
logger.setLevel(logging.NOTSET) # this sets the minimum log level
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
else:
|
||||
_LOGGER.error(
|
||||
|
@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
alarms.extend([
|
||||
VerisureAlarm(value)
|
||||
for value in verisure.get_alarm_status().values()
|
||||
for value in verisure.ALARM_STATUS.values()
|
||||
if verisure.SHOW_ALARM
|
||||
])
|
||||
|
||||
@ -42,7 +42,6 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def __init__(self, alarm_status):
|
||||
self._id = alarm_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_ALARM
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
@ -62,36 +61,36 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def update(self):
|
||||
""" Update alarm status """
|
||||
verisure.update()
|
||||
verisure.update_alarm()
|
||||
|
||||
if verisure.STATUS[self._device][self._id].status == 'unarmed':
|
||||
if verisure.ALARM_STATUS[self._id].status == 'unarmed':
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
|
||||
elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
|
||||
elif verisure.ALARM_STATUS[self._id].status == 'armedaway':
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif verisure.STATUS[self._device][self._id].status != 'pending':
|
||||
elif verisure.ALARM_STATUS[self._id].status != 'pending':
|
||||
_LOGGER.error(
|
||||
'Unknown alarm state %s',
|
||||
verisure.STATUS[self._device][self._id].status)
|
||||
verisure.ALARM_STATUS[self._id].status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_DISARMED)
|
||||
_LOGGER.warning('disarming')
|
||||
verisure.MY_PAGES.alarm.set(code, 'DISARMED')
|
||||
_LOGGER.info('verisure alarm disarming')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_ARMED_HOME)
|
||||
_LOGGER.warning('arming home')
|
||||
verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME')
|
||||
_LOGGER.info('verisure alarm arming home')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
verisure.MY_PAGES.set_alarm_status(
|
||||
code,
|
||||
verisure.MY_PAGES.ALARM_ARMED_AWAY)
|
||||
_LOGGER.warning('arming away')
|
||||
verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY')
|
||||
_LOGGER.info('verisure alarm arming away')
|
||||
verisure.MY_PAGES.alarm.wait_while_pending()
|
||||
verisure.update_alarm()
|
||||
|
@ -116,7 +116,7 @@ class AlexaResponse(object):
|
||||
self.should_end_session = True
|
||||
if intent is not None and 'slots' in intent:
|
||||
self.variables = {key: value['value'] for key, value
|
||||
in intent['slots'].items()}
|
||||
in intent['slots'].items() if 'value' in value}
|
||||
else:
|
||||
self.variables = {}
|
||||
|
||||
|
@ -17,6 +17,10 @@ DEPENDENCIES = ['sun']
|
||||
|
||||
CONF_OFFSET = 'offset'
|
||||
CONF_EVENT = 'event'
|
||||
CONF_BEFORE = "before"
|
||||
CONF_BEFORE_OFFSET = "before_offset"
|
||||
CONF_AFTER = "after"
|
||||
CONF_AFTER_OFFSET = "after_offset"
|
||||
|
||||
EVENT_SUNSET = 'sunset'
|
||||
EVENT_SUNRISE = 'sunrise'
|
||||
@ -37,26 +41,9 @@ def trigger(hass, config, action):
|
||||
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
|
||||
return False
|
||||
|
||||
if CONF_OFFSET in config:
|
||||
raw_offset = config.get(CONF_OFFSET)
|
||||
|
||||
negative_offset = False
|
||||
if raw_offset.startswith('-'):
|
||||
negative_offset = True
|
||||
raw_offset = raw_offset[1:]
|
||||
|
||||
try:
|
||||
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
|
||||
except ValueError:
|
||||
_LOGGER.error('Could not parse offset %s', raw_offset)
|
||||
return False
|
||||
|
||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||
|
||||
if negative_offset:
|
||||
offset *= -1
|
||||
else:
|
||||
offset = timedelta(0)
|
||||
offset = _parse_offset(config.get(CONF_OFFSET))
|
||||
if offset is False:
|
||||
return False
|
||||
|
||||
# Do something to call action
|
||||
if event == EVENT_SUNRISE:
|
||||
@ -67,6 +54,65 @@ def trigger(hass, config, action):
|
||||
return True
|
||||
|
||||
|
||||
def if_action(hass, config):
|
||||
""" Wraps action method with sun based condition. """
|
||||
before = config.get(CONF_BEFORE)
|
||||
after = config.get(CONF_AFTER)
|
||||
|
||||
# Make sure required configuration keys are present
|
||||
if before is None and after is None:
|
||||
logging.getLogger(__name__).error(
|
||||
"Missing if-condition configuration key %s or %s",
|
||||
CONF_BEFORE, CONF_AFTER)
|
||||
return None
|
||||
|
||||
# Make sure configuration keys have the right value
|
||||
if before not in (None, EVENT_SUNRISE, EVENT_SUNSET) or \
|
||||
after not in (None, EVENT_SUNRISE, EVENT_SUNSET):
|
||||
logging.getLogger(__name__).error(
|
||||
"%s and %s can only be set to %s or %s",
|
||||
CONF_BEFORE, CONF_AFTER, EVENT_SUNRISE, EVENT_SUNSET)
|
||||
return None
|
||||
|
||||
before_offset = _parse_offset(config.get(CONF_BEFORE_OFFSET))
|
||||
after_offset = _parse_offset(config.get(CONF_AFTER_OFFSET))
|
||||
if before_offset is False or after_offset is False:
|
||||
return None
|
||||
|
||||
if before is None:
|
||||
before_func = lambda: None
|
||||
elif before == EVENT_SUNRISE:
|
||||
before_func = lambda: sun.next_rising_utc(hass) + before_offset
|
||||
else:
|
||||
before_func = lambda: sun.next_setting_utc(hass) + before_offset
|
||||
|
||||
if after is None:
|
||||
after_func = lambda: None
|
||||
elif after == EVENT_SUNRISE:
|
||||
after_func = lambda: sun.next_rising_utc(hass) + after_offset
|
||||
else:
|
||||
after_func = lambda: sun.next_setting_utc(hass) + after_offset
|
||||
|
||||
def time_if():
|
||||
""" Validate time based if-condition """
|
||||
|
||||
now = dt_util.utcnow()
|
||||
before = before_func()
|
||||
after = after_func()
|
||||
|
||||
if before is not None and now > now.replace(hour=before.hour,
|
||||
minute=before.minute):
|
||||
return False
|
||||
|
||||
if after is not None and now < now.replace(hour=after.hour,
|
||||
minute=after.minute):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return time_if
|
||||
|
||||
|
||||
def trigger_sunrise(hass, action, offset):
|
||||
""" Trigger action at next sun rise. """
|
||||
def next_rise():
|
||||
@ -103,3 +149,26 @@ def trigger_sunset(hass, action, offset):
|
||||
action()
|
||||
|
||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||
|
||||
|
||||
def _parse_offset(raw_offset):
|
||||
if raw_offset is None:
|
||||
return timedelta(0)
|
||||
|
||||
negative_offset = False
|
||||
if raw_offset.startswith('-'):
|
||||
negative_offset = True
|
||||
raw_offset = raw_offset[1:]
|
||||
|
||||
try:
|
||||
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
|
||||
except ValueError:
|
||||
_LOGGER.error('Could not parse offset %s', raw_offset)
|
||||
return False
|
||||
|
||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||
|
||||
if negative_offset:
|
||||
offset *= -1
|
||||
|
||||
return offset
|
||||
|
@ -47,15 +47,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
response = requests.post(resource, data=payload, timeout=10,
|
||||
verify=verify_ssl)
|
||||
if not response.ok:
|
||||
_LOGGER.error('Response status is "%s"', response.status_code)
|
||||
_LOGGER.error("Response status is '%s'", response.status_code)
|
||||
return False
|
||||
except requests.exceptions.MissingSchema:
|
||||
_LOGGER.error('Missing resource or schema in configuration. '
|
||||
'Add http:// to your URL.')
|
||||
_LOGGER.error("Missing resource or schema in configuration. "
|
||||
"Add http:// or https:// to your URL")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error('No route to resource/endpoint: %s',
|
||||
resource)
|
||||
_LOGGER.error('No route to resource/endpoint: %s', resource)
|
||||
return False
|
||||
|
||||
if use_get:
|
||||
|
@ -1,70 +0,0 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.geofancy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Geofancy platform for the device tracker.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.geofancy/
|
||||
"""
|
||||
from homeassistant.const import (
|
||||
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
_SEE = 0
|
||||
|
||||
URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up an endpoint for the Geofancy app. """
|
||||
|
||||
# Use a global variable to keep setup_scanner compact when using a callback
|
||||
global _SEE
|
||||
_SEE = see
|
||||
|
||||
# POST would be semantically better, but that currently does not work
|
||||
# since Geofancy sends the data as key1=value1&key2=value2
|
||||
# in the request body, while Home Assistant expects json there.
|
||||
|
||||
hass.http.register_path(
|
||||
'GET', URL_API_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _handle_get_api_geofancy(handler, path_match, data):
|
||||
""" Geofancy message received. """
|
||||
|
||||
if not isinstance(data, dict):
|
||||
handler.write_json_message(
|
||||
"Error while parsing Geofancy message.",
|
||||
HTTP_INTERNAL_SERVER_ERROR)
|
||||
return
|
||||
if 'latitude' not in data or 'longitude' not in data:
|
||||
handler.write_json_message(
|
||||
"Location not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
return
|
||||
if 'device' not in data or 'id' not in data:
|
||||
handler.write_json_message(
|
||||
"Device id or location id not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
return
|
||||
|
||||
try:
|
||||
gps_coords = (float(data['latitude']), float(data['longitude']))
|
||||
except ValueError:
|
||||
# If invalid latitude / longitude format
|
||||
handler.write_json_message(
|
||||
"Invalid latitude / longitude format.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
return
|
||||
|
||||
# entity id's in Home Assistant must be alphanumerical
|
||||
device_uuid = data['device']
|
||||
device_entity_id = device_uuid.replace('-', '')
|
||||
|
||||
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
|
||||
|
||||
handler.write_json_message("Geofancy message processed")
|
105
homeassistant/components/device_tracker/locative.py
Normal file
105
homeassistant/components/device_tracker/locative.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.locative
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Locative platform for the device tracker.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.locative/
|
||||
"""
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from homeassistant.const import (
|
||||
HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME)
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
URL_API_LOCATIVE_ENDPOINT = "/api/locative"
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up an endpoint for the Locative app. """
|
||||
|
||||
# POST would be semantically better, but that currently does not work
|
||||
# since Locative sends the data as key1=value1&key2=value2
|
||||
# in the request body, while Home Assistant expects json there.
|
||||
|
||||
hass.http.register_path(
|
||||
'GET', URL_API_LOCATIVE_ENDPOINT,
|
||||
partial(_handle_get_api_locative, hass, see))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _handle_get_api_locative(hass, see, handler, path_match, data):
|
||||
""" Locative message received. """
|
||||
|
||||
if not _check_data(handler, data):
|
||||
return
|
||||
|
||||
device = data['device'].replace('-', '')
|
||||
location_name = data['id'].lower()
|
||||
direction = data['trigger']
|
||||
|
||||
if direction == 'enter':
|
||||
see(dev_id=device, location_name=location_name)
|
||||
handler.write_text("Setting location to {}".format(location_name))
|
||||
|
||||
elif direction == 'exit':
|
||||
current_state = hass.states.get(
|
||||
"{}.{}".format(DOMAIN, device)).state
|
||||
|
||||
if current_state == location_name:
|
||||
see(dev_id=device, location_name=STATE_NOT_HOME)
|
||||
handler.write_text("Setting location to not home")
|
||||
else:
|
||||
# Ignore the message if it is telling us to exit a zone that we
|
||||
# aren't currently in. This occurs when a zone is entered before
|
||||
# the previous zone was exited. The enter message will be sent
|
||||
# first, then the exit message will be sent second.
|
||||
handler.write_text(
|
||||
'Ignoring exit from {} (already in {})'.format(
|
||||
location_name, current_state))
|
||||
|
||||
elif direction == 'test':
|
||||
# In the app, a test message can be sent. Just return something to
|
||||
# the user to let them know that it works.
|
||||
handler.write_text("Received test message.")
|
||||
|
||||
else:
|
||||
handler.write_text(
|
||||
"Received unidentified message: {}".format(direction),
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.error("Received unidentified message from Locative: %s",
|
||||
direction)
|
||||
|
||||
|
||||
def _check_data(handler, data):
|
||||
if 'latitude' not in data or 'longitude' not in data:
|
||||
handler.write_text("Latitude and longitude not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.error("Latitude and longitude not specified.")
|
||||
return False
|
||||
|
||||
if 'device' not in data:
|
||||
handler.write_text("Device id not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.error("Device id not specified.")
|
||||
return False
|
||||
|
||||
if 'id' not in data:
|
||||
handler.write_text("Location id not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.error("Location id not specified.")
|
||||
return False
|
||||
|
||||
if 'trigger' not in data:
|
||||
handler.write_text("Trigger is not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
_LOGGER.error("Trigger is not specified.")
|
||||
return False
|
||||
|
||||
return True
|
@ -21,7 +21,7 @@ from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.const import (
|
||||
SERVER_PORT, CONTENT_TYPE_JSON,
|
||||
SERVER_PORT, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN,
|
||||
HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
|
||||
HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH,
|
||||
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED,
|
||||
@ -293,6 +293,17 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
json.dumps(data, indent=4, sort_keys=True,
|
||||
cls=rem.JSONEncoder).encode("UTF-8"))
|
||||
|
||||
def write_text(self, message, status_code=HTTP_OK):
|
||||
""" Helper method to return a text message to the caller. """
|
||||
self.send_response(status_code)
|
||||
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
|
||||
|
||||
self.set_session_cookie_header()
|
||||
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(message.encode("UTF-8"))
|
||||
|
||||
def write_file(self, path, cache_headers=True):
|
||||
""" Returns a file to the user. """
|
||||
try:
|
||||
|
@ -50,6 +50,7 @@ FLASH_LONG = "long"
|
||||
# Apply an effect to the light, can be EFFECT_COLORLOOP
|
||||
ATTR_EFFECT = "effect"
|
||||
EFFECT_COLORLOOP = "colorloop"
|
||||
EFFECT_RANDOM = "random"
|
||||
EFFECT_WHITE = "white"
|
||||
|
||||
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||
@ -228,7 +229,8 @@ def setup(hass, config):
|
||||
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
|
||||
params[ATTR_FLASH] = dat[ATTR_FLASH]
|
||||
|
||||
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE):
|
||||
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE,
|
||||
EFFECT_RANDOM):
|
||||
params[ATTR_EFFECT] = dat[ATTR_EFFECT]
|
||||
|
||||
for light in target_lights:
|
||||
|
@ -10,6 +10,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@ -20,7 +21,7 @@ from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME
|
||||
from homeassistant.components.light import (
|
||||
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
|
||||
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
|
||||
ATTR_EFFECT, EFFECT_COLORLOOP, ATTR_RGB_COLOR)
|
||||
ATTR_EFFECT, EFFECT_COLORLOOP, EFFECT_RANDOM, ATTR_RGB_COLOR)
|
||||
|
||||
REQUIREMENTS = ['phue==0.8']
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
@ -120,10 +121,17 @@ def setup_bridge(host, hass, add_devices_callback):
|
||||
|
||||
new_lights = []
|
||||
|
||||
api_name = api.get('config').get('name')
|
||||
if api_name == 'RaspBee-GW':
|
||||
bridge_type = 'deconz'
|
||||
else:
|
||||
bridge_type = 'hue'
|
||||
|
||||
for light_id, info in api_states.items():
|
||||
if light_id not in lights:
|
||||
lights[light_id] = HueLight(int(light_id), info,
|
||||
bridge, update_lights)
|
||||
bridge, update_lights,
|
||||
bridge_type=bridge_type)
|
||||
new_lights.append(lights[light_id])
|
||||
else:
|
||||
lights[light_id].info = info
|
||||
@ -162,11 +170,14 @@ def request_configuration(host, hass, add_devices_callback):
|
||||
class HueLight(Light):
|
||||
""" Represents a Hue light """
|
||||
|
||||
def __init__(self, light_id, info, bridge, update_lights):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, light_id, info, bridge, update_lights,
|
||||
bridge_type='hue'):
|
||||
self.light_id = light_id
|
||||
self.info = info
|
||||
self.bridge = bridge
|
||||
self.update_lights = update_lights
|
||||
self.bridge_type = bridge_type
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
@ -226,14 +237,17 @@ class HueLight(Light):
|
||||
command['alert'] = 'lselect'
|
||||
elif flash == FLASH_SHORT:
|
||||
command['alert'] = 'select'
|
||||
else:
|
||||
elif self.bridge_type == 'hue':
|
||||
command['alert'] = 'none'
|
||||
|
||||
effect = kwargs.get(ATTR_EFFECT)
|
||||
|
||||
if effect == EFFECT_COLORLOOP:
|
||||
command['effect'] = 'colorloop'
|
||||
else:
|
||||
elif effect == EFFECT_RANDOM:
|
||||
command['hue'] = random.randrange(0, 65535)
|
||||
command['sat'] = random.randrange(150, 254)
|
||||
elif self.bridge_type == 'hue':
|
||||
command['effect'] = 'none'
|
||||
|
||||
self.bridge.set_light(self.light_id, command)
|
||||
|
@ -42,6 +42,7 @@ turn_on:
|
||||
description: Light effect
|
||||
values:
|
||||
- colorloop
|
||||
- random
|
||||
|
||||
turn_off:
|
||||
description: Turn a light off
|
||||
|
@ -14,9 +14,9 @@ from homeassistant.components.switch.vera import VeraSwitch
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
|
||||
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
|
||||
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
|
||||
'#python-vera==0.1.1']
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['pyvera==0.2.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -36,10 +36,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
device_data = config.get('device_data', {})
|
||||
|
||||
controller = veraApi.VeraController(base_url)
|
||||
vera_controller, created = veraApi.init_controller(base_url)
|
||||
|
||||
if created:
|
||||
def stop_subscription(event):
|
||||
""" Shutdown Vera subscriptions and subscription thread on exit"""
|
||||
_LOGGER.info("Shutting down subscriptions.")
|
||||
vera_controller.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
||||
|
||||
devices = []
|
||||
try:
|
||||
devices = controller.get_devices([
|
||||
devices = vera_controller.get_devices([
|
||||
'Switch',
|
||||
'On/Off Switch',
|
||||
'Dimmable Switch'])
|
||||
@ -54,7 +63,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
exclude = extra_data.get('exclude', False)
|
||||
|
||||
if exclude is not True:
|
||||
lights.append(VeraLight(device, extra_data))
|
||||
lights.append(VeraLight(device, vera_controller, extra_data))
|
||||
|
||||
add_devices_callback(lights)
|
||||
|
||||
|
@ -28,7 +28,7 @@ QUERY_EVENTS_BETWEEN = """
|
||||
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
|
||||
"""
|
||||
|
||||
EVENT_LOGBOOK_ENTRY = 'LOGBOOK_ENTRY'
|
||||
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
|
||||
|
||||
GROUP_BY_MINUTES = 15
|
||||
|
||||
@ -204,7 +204,7 @@ def humanify(events):
|
||||
event.time_fired, "Home Assistant", action,
|
||||
domain=HA_DOMAIN)
|
||||
|
||||
elif event.event_type == EVENT_LOGBOOK_ENTRY:
|
||||
elif event.event_type.lower() == EVENT_LOGBOOK_ENTRY:
|
||||
domain = event.data.get(ATTR_DOMAIN)
|
||||
entity_id = event.data.get(ATTR_ENTITY_ID)
|
||||
if domain is None and entity_id is not None:
|
||||
|
@ -76,8 +76,12 @@ def setup(hass, config=None):
|
||||
|
||||
logfilter[LOGGER_LOGS] = logs
|
||||
|
||||
logger = logging.getLogger('')
|
||||
logger.setLevel(logging.NOTSET)
|
||||
|
||||
# Set log filter for all log handler
|
||||
for handler in logging.root.handlers:
|
||||
handler.setLevel(logging.NOTSET)
|
||||
handler.addFilter(HomeAssistantLogFilter(logfilter))
|
||||
|
||||
return True
|
||||
|
@ -142,10 +142,14 @@ class MpdDevice(MediaPlayerDevice):
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
name = self.currentsong.get('name', None)
|
||||
title = self.currentsong['title']
|
||||
title = self.currentsong.get('title', None)
|
||||
|
||||
if name is None:
|
||||
if name is None and title is None:
|
||||
return "None"
|
||||
elif name is None:
|
||||
return title
|
||||
elif title is None:
|
||||
return name
|
||||
else:
|
||||
return '{}: {}'.format(name, title)
|
||||
|
||||
|
@ -30,7 +30,7 @@ DEFAULT_QOS = 0
|
||||
DEFAULT_RETAIN = False
|
||||
|
||||
SERVICE_PUBLISH = 'publish'
|
||||
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
||||
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
|
||||
|
||||
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||
|
||||
|
@ -16,7 +16,7 @@ import json
|
||||
import atexit
|
||||
|
||||
from homeassistant.core import Event, EventOrigin, State
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.remote import JSONEncoder
|
||||
from homeassistant.const import (
|
||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||
@ -62,8 +62,8 @@ def row_to_state(row):
|
||||
try:
|
||||
return State(
|
||||
row[1], row[2], json.loads(row[3]),
|
||||
date_util.utc_from_timestamp(row[4]),
|
||||
date_util.utc_from_timestamp(row[5]))
|
||||
dt_util.utc_from_timestamp(row[4]),
|
||||
dt_util.utc_from_timestamp(row[5]))
|
||||
except ValueError:
|
||||
# When json.loads fails
|
||||
_LOGGER.exception("Error converting row to state: %s", row)
|
||||
@ -74,7 +74,7 @@ def row_to_event(row):
|
||||
""" Convert a databse row to an event. """
|
||||
try:
|
||||
return Event(row[1], json.loads(row[2]), EventOrigin(row[3]),
|
||||
date_util.utc_from_timestamp(row[5]))
|
||||
dt_util.utc_from_timestamp(row[5]))
|
||||
except ValueError:
|
||||
# When json.loads fails
|
||||
_LOGGER.exception("Error converting row to event: %s", row)
|
||||
@ -116,10 +116,10 @@ class RecorderRun(object):
|
||||
self.start = _INSTANCE.recording_start
|
||||
self.closed_incorrect = False
|
||||
else:
|
||||
self.start = date_util.utc_from_timestamp(row[1])
|
||||
self.start = dt_util.utc_from_timestamp(row[1])
|
||||
|
||||
if row[2] is not None:
|
||||
self.end = date_util.utc_from_timestamp(row[2])
|
||||
self.end = dt_util.utc_from_timestamp(row[2])
|
||||
|
||||
self.closed_incorrect = bool(row[3])
|
||||
|
||||
@ -169,8 +169,8 @@ class Recorder(threading.Thread):
|
||||
self.queue = queue.Queue()
|
||||
self.quit_object = object()
|
||||
self.lock = threading.Lock()
|
||||
self.recording_start = date_util.utcnow()
|
||||
self.utc_offset = date_util.now().utcoffset().total_seconds()
|
||||
self.recording_start = dt_util.utcnow()
|
||||
self.utc_offset = dt_util.now().utcoffset().total_seconds()
|
||||
|
||||
def start_recording(event):
|
||||
""" Start recording. """
|
||||
@ -217,10 +217,11 @@ class Recorder(threading.Thread):
|
||||
def shutdown(self, event):
|
||||
""" Tells the recorder to shut down. """
|
||||
self.queue.put(self.quit_object)
|
||||
self.block_till_done()
|
||||
|
||||
def record_state(self, entity_id, state, event_id):
|
||||
""" Save a state to the database. """
|
||||
now = date_util.utcnow()
|
||||
now = dt_util.utcnow()
|
||||
|
||||
# State got deleted
|
||||
if state is None:
|
||||
@ -247,7 +248,7 @@ class Recorder(threading.Thread):
|
||||
""" Save an event to the database. """
|
||||
info = (
|
||||
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
||||
str(event.origin), date_util.utcnow(), event.time_fired,
|
||||
str(event.origin), dt_util.utcnow(), event.time_fired,
|
||||
self.utc_offset
|
||||
)
|
||||
|
||||
@ -307,7 +308,7 @@ class Recorder(threading.Thread):
|
||||
def save_migration(migration_id):
|
||||
""" Save and commit a migration to the database. """
|
||||
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
||||
(migration_id, date_util.utcnow()))
|
||||
(migration_id, dt_util.utcnow()))
|
||||
self.conn.commit()
|
||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||
|
||||
@ -420,18 +421,18 @@ class Recorder(threading.Thread):
|
||||
self.query(
|
||||
"""INSERT INTO recorder_runs (start, created, utc_offset)
|
||||
VALUES (?, ?, ?)""",
|
||||
(self.recording_start, date_util.utcnow(), self.utc_offset))
|
||||
(self.recording_start, dt_util.utcnow(), self.utc_offset))
|
||||
|
||||
def _close_run(self):
|
||||
""" Save end time for current run. """
|
||||
self.query(
|
||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||
(date_util.utcnow(), self.recording_start))
|
||||
(dt_util.utcnow(), self.recording_start))
|
||||
|
||||
|
||||
def _adapt_datetime(datetimestamp):
|
||||
""" Turn a datetime into an integer for in the DB. """
|
||||
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||
return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||
|
||||
|
||||
def _verify_instance():
|
||||
|
@ -73,8 +73,9 @@ def _process_config(scene_config):
|
||||
|
||||
for entity_id in c_entities:
|
||||
if isinstance(c_entities[entity_id], dict):
|
||||
state = c_entities[entity_id].pop('state', None)
|
||||
attributes = c_entities[entity_id]
|
||||
entity_attrs = c_entities[entity_id].copy()
|
||||
state = entity_attrs.pop('state', None)
|
||||
attributes = entity_attrs
|
||||
else:
|
||||
state = c_entities[entity_id]
|
||||
attributes = {}
|
||||
|
@ -9,7 +9,8 @@ https://home-assistant.io/components/sensor/
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.components import wink, zwave, isy994, verisure, ecobee
|
||||
from homeassistant.components import (wink, zwave, isy994,
|
||||
verisure, ecobee, tellduslive)
|
||||
|
||||
DOMAIN = 'sensor'
|
||||
SCAN_INTERVAL = 30
|
||||
@ -22,7 +23,8 @@ DISCOVERY_PLATFORMS = {
|
||||
zwave.DISCOVER_SENSORS: 'zwave',
|
||||
isy994.DISCOVER_SENSORS: 'isy994',
|
||||
verisure.DISCOVER_SENSORS: 'verisure',
|
||||
ecobee.DISCOVER_SENSORS: 'ecobee'
|
||||
ecobee.DISCOVER_SENSORS: 'ecobee',
|
||||
tellduslive.DISCOVER_SENSORS: 'tellduslive',
|
||||
}
|
||||
|
||||
|
||||
|
@ -53,6 +53,11 @@ class EliqSensor(Entity):
|
||||
""" Returns the name. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Returns icon. """
|
||||
return "mdi:speedometer"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
|
@ -10,7 +10,7 @@ from datetime import timedelta
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.const import (CONF_VALUE_TEMPLATE, STATE_UNKNOWN)
|
||||
from homeassistant.util import template, Throttle
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
@ -47,15 +47,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
response = requests.post(resource, data=payload, timeout=10,
|
||||
verify=verify_ssl)
|
||||
if not response.ok:
|
||||
_LOGGER.error('Response status is "%s"', response.status_code)
|
||||
_LOGGER.error("Response status is '%s'", response.status_code)
|
||||
return False
|
||||
except requests.exceptions.MissingSchema:
|
||||
_LOGGER.error('Missing resource or schema in configuration. '
|
||||
'Add http:// to your URL.')
|
||||
_LOGGER.error("Missing resource or schema in configuration. "
|
||||
"Add http:// or https:// to your URL")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error('No route to resource/endpoint. '
|
||||
'Please check the URL in the configuration file.')
|
||||
_LOGGER.error("No route to resource/endpoint: %s", resource)
|
||||
return False
|
||||
|
||||
if use_get:
|
||||
@ -78,7 +77,7 @@ class RestSensor(Entity):
|
||||
self._hass = hass
|
||||
self.rest = rest
|
||||
self._name = name
|
||||
self._state = 'n/a'
|
||||
self._state = STATE_UNKNOWN
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
@ -103,13 +102,13 @@ class RestSensor(Entity):
|
||||
self.rest.update()
|
||||
value = self.rest.data
|
||||
|
||||
if 'error' in value:
|
||||
self._state = value['error']
|
||||
else:
|
||||
if self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, 'N/A')
|
||||
self._state = value
|
||||
if value is None:
|
||||
value = STATE_UNKNOWN
|
||||
elif self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, STATE_UNKNOWN)
|
||||
|
||||
self._state = value
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
@ -119,7 +118,7 @@ class RestDataGet(object):
|
||||
def __init__(self, resource, verify_ssl):
|
||||
self._resource = resource
|
||||
self._verify_ssl = verify_ssl
|
||||
self.data = dict()
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
@ -127,12 +126,10 @@ class RestDataGet(object):
|
||||
try:
|
||||
response = requests.get(self._resource, timeout=10,
|
||||
verify=self._verify_ssl)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.text
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint.")
|
||||
self.data['error'] = 'N/A'
|
||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
||||
self.data = None
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
@ -143,7 +140,7 @@ class RestDataPost(object):
|
||||
self._resource = resource
|
||||
self._payload = payload
|
||||
self._verify_ssl = verify_ssl
|
||||
self.data = dict()
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
@ -151,9 +148,7 @@ class RestDataPost(object):
|
||||
try:
|
||||
response = requests.post(self._resource, data=self._payload,
|
||||
timeout=10, verify=self._verify_ssl)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.text
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint.")
|
||||
self.data['error'] = 'N/A'
|
||||
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
|
||||
self.data = None
|
||||
|
@ -14,25 +14,25 @@ from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
REQUIREMENTS = ['psutil==3.2.2']
|
||||
SENSOR_TYPES = {
|
||||
'disk_use_percent': ['Disk Use', '%'],
|
||||
'disk_use': ['Disk Use', 'GiB'],
|
||||
'disk_free': ['Disk Free', 'GiB'],
|
||||
'memory_use_percent': ['RAM Use', '%'],
|
||||
'memory_use': ['RAM Use', 'MiB'],
|
||||
'memory_free': ['RAM Free', 'MiB'],
|
||||
'processor_use': ['CPU Use', '%'],
|
||||
'process': ['Process', ''],
|
||||
'swap_use_percent': ['Swap Use', '%'],
|
||||
'swap_use': ['Swap Use', 'GiB'],
|
||||
'swap_free': ['Swap Free', 'GiB'],
|
||||
'network_out': ['Sent', 'MiB'],
|
||||
'network_in': ['Recieved', 'MiB'],
|
||||
'packets_out': ['Packets sent', ''],
|
||||
'packets_in': ['Packets recieved', ''],
|
||||
'ipv4_address': ['IPv4 address', ''],
|
||||
'ipv6_address': ['IPv6 address', ''],
|
||||
'last_boot': ['Last Boot', ''],
|
||||
'since_last_boot': ['Since Last Boot', '']
|
||||
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||
'disk_free': ['Disk Free', 'GiB', 'mdi:harddisk'],
|
||||
'memory_use_percent': ['RAM Use', '%', 'mdi:memory'],
|
||||
'memory_use': ['RAM Use', 'MiB', 'mdi:memory'],
|
||||
'memory_free': ['RAM Free', 'MiB', 'mdi:memory'],
|
||||
'processor_use': ['CPU Use', '%', 'mdi:memory'],
|
||||
'process': ['Process', '', 'mdi:memory'],
|
||||
'swap_use_percent': ['Swap Use', '%', 'mdi:harddisk'],
|
||||
'swap_use': ['Swap Use', 'GiB', 'mdi:harddisk'],
|
||||
'swap_free': ['Swap Free', 'GiB', 'mdi:harddisk'],
|
||||
'network_out': ['Sent', 'MiB', 'mdi:server-network'],
|
||||
'network_in': ['Recieved', 'MiB', 'mdi:server-network'],
|
||||
'packets_out': ['Packets sent', '', 'mdi:server-network'],
|
||||
'packets_in': ['Packets recieved', '', 'mdi:server-network'],
|
||||
'ipv4_address': ['IPv4 address', '', 'mdi:server-network'],
|
||||
'ipv6_address': ['IPv6 address', '', 'mdi:server-network'],
|
||||
'last_boot': ['Last Boot', '', 'mdi:clock'],
|
||||
'since_last_boot': ['Since Last Boot', '', 'mdi:clock']
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -69,6 +69,10 @@ class SystemMonitorSensor(Entity):
|
||||
def name(self):
|
||||
return self._name.rstrip()
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return SENSOR_TYPES[self.type][2]
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
|
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))
|
117
homeassistant/components/sensor/torque.py
Normal file
117
homeassistant/components/sensor/torque.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""
|
||||
homeassistant.components.sensor.torque
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Get data from the Torque OBD application.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.torque/
|
||||
"""
|
||||
|
||||
import re
|
||||
from homeassistant.const import HTTP_OK
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
|
||||
DOMAIN = 'torque'
|
||||
DEPENDENCIES = ['http']
|
||||
SENSOR_EMAIL_FIELD = 'eml'
|
||||
DEFAULT_NAME = 'vehicle'
|
||||
ENTITY_NAME_FORMAT = '{0} {1}'
|
||||
|
||||
API_PATH = '/api/torque'
|
||||
SENSOR_NAME_KEY = r'userFullName(\w+)'
|
||||
SENSOR_UNIT_KEY = r'userUnit(\w+)'
|
||||
SENSOR_VALUE_KEY = r'k(\w+)'
|
||||
|
||||
NAME_KEY = re.compile(SENSOR_NAME_KEY)
|
||||
UNIT_KEY = re.compile(SENSOR_UNIT_KEY)
|
||||
VALUE_KEY = re.compile(SENSOR_VALUE_KEY)
|
||||
|
||||
|
||||
def decode(value):
|
||||
""" Double-decode required. """
|
||||
return value.encode('raw_unicode_escape').decode('utf-8')
|
||||
|
||||
|
||||
def convert_pid(value):
|
||||
""" Convert pid from hex string to integer. """
|
||||
return int(value, 16)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Set up Torque platform. """
|
||||
|
||||
vehicle = config.get('name', DEFAULT_NAME)
|
||||
email = config.get('email', None)
|
||||
sensors = {}
|
||||
|
||||
def _receive_data(handler, path_match, data):
|
||||
""" Received data from Torque. """
|
||||
handler.send_response(HTTP_OK)
|
||||
handler.end_headers()
|
||||
|
||||
if email is not None and email != data[SENSOR_EMAIL_FIELD]:
|
||||
return
|
||||
|
||||
names = {}
|
||||
units = {}
|
||||
for key in data:
|
||||
is_name = NAME_KEY.match(key)
|
||||
is_unit = UNIT_KEY.match(key)
|
||||
is_value = VALUE_KEY.match(key)
|
||||
|
||||
if is_name:
|
||||
pid = convert_pid(is_name.group(1))
|
||||
names[pid] = decode(data[key])
|
||||
elif is_unit:
|
||||
pid = convert_pid(is_unit.group(1))
|
||||
units[pid] = decode(data[key])
|
||||
elif is_value:
|
||||
pid = convert_pid(is_value.group(1))
|
||||
if pid in sensors:
|
||||
sensors[pid].on_update(data[key])
|
||||
|
||||
for pid in names:
|
||||
if pid not in sensors:
|
||||
sensors[pid] = TorqueSensor(
|
||||
ENTITY_NAME_FORMAT.format(vehicle, names[pid]),
|
||||
units.get(pid, None))
|
||||
add_devices([sensors[pid]])
|
||||
|
||||
hass.http.register_path('GET', API_PATH, _receive_data)
|
||||
return True
|
||||
|
||||
|
||||
class TorqueSensor(Entity):
|
||||
""" Represents a Torque sensor. """
|
||||
|
||||
def __init__(self, name, unit):
|
||||
self._name = name
|
||||
self._unit = unit
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the sensor. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Returns the unit of measurement. """
|
||||
return self._unit
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the sensor. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Sensor default icon. """
|
||||
return 'mdi:car'
|
||||
|
||||
def on_update(self, value):
|
||||
""" Receive an update. """
|
||||
self._state = value
|
||||
self.update_ha_state()
|
@ -13,11 +13,9 @@ import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
||||
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
|
||||
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
|
||||
'#python-vera==0.1.1']
|
||||
REQUIREMENTS = ['pyvera==0.2.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -37,7 +35,16 @@ def get_devices(hass, config):
|
||||
|
||||
device_data = config.get('device_data', {})
|
||||
|
||||
vera_controller = veraApi.VeraController(base_url)
|
||||
vera_controller, created = veraApi.init_controller(base_url)
|
||||
|
||||
if created:
|
||||
def stop_subscription(event):
|
||||
""" Shutdown Vera subscriptions and subscription thread on exit"""
|
||||
_LOGGER.info("Shutting down subscriptions.")
|
||||
vera_controller.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
||||
|
||||
categories = ['Temperature Sensor', 'Light Sensor', 'Sensor']
|
||||
devices = []
|
||||
try:
|
||||
@ -53,7 +60,8 @@ def get_devices(hass, config):
|
||||
exclude = extra_data.get('exclude', False)
|
||||
|
||||
if exclude is not True:
|
||||
vera_sensors.append(VeraSensor(device, extra_data))
|
||||
vera_sensors.append(
|
||||
VeraSensor(device, vera_controller, extra_data))
|
||||
|
||||
return vera_sensors
|
||||
|
||||
@ -66,8 +74,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class VeraSensor(Entity):
|
||||
""" Represents a Vera Sensor. """
|
||||
|
||||
def __init__(self, vera_device, extra_data=None):
|
||||
def __init__(self, vera_device, controller, extra_data=None):
|
||||
self.vera_device = vera_device
|
||||
self.controller = controller
|
||||
self.extra_data = extra_data
|
||||
if self.extra_data and self.extra_data.get('name'):
|
||||
self._name = self.extra_data.get('name')
|
||||
@ -76,6 +85,16 @@ class VeraSensor(Entity):
|
||||
self.current_value = ''
|
||||
self._temperature_units = None
|
||||
|
||||
self.controller.register(vera_device)
|
||||
self.controller.on(
|
||||
vera_device, self._update_callback)
|
||||
|
||||
def _update_callback(self, _device):
|
||||
""" Called by the vera device callback to update state. """
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s', self.name)
|
||||
self.update_ha_state(True)
|
||||
|
||||
def __str__(self):
|
||||
return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state)
|
||||
|
||||
@ -117,6 +136,11 @@ class VeraSensor(Entity):
|
||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||
return attr
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
if self.vera_device.category == "Temperature Sensor":
|
||||
self.vera_device.refresh_value('CurrentTemperature')
|
||||
|
@ -27,14 +27,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
sensors.extend([
|
||||
VerisureThermometer(value)
|
||||
for value in verisure.get_climate_status().values()
|
||||
for value in verisure.CLIMATE_STATUS.values()
|
||||
if verisure.SHOW_THERMOMETERS and
|
||||
hasattr(value, 'temperature') and value.temperature
|
||||
])
|
||||
|
||||
sensors.extend([
|
||||
VerisureHygrometer(value)
|
||||
for value in verisure.get_climate_status().values()
|
||||
for value in verisure.CLIMATE_STATUS.values()
|
||||
if verisure.SHOW_HYGROMETERS and
|
||||
hasattr(value, 'humidity') and value.humidity
|
||||
])
|
||||
@ -47,20 +47,19 @@ class VerisureThermometer(Entity):
|
||||
|
||||
def __init__(self, climate_status):
|
||||
self._id = climate_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return '{} {}'.format(
|
||||
verisure.STATUS[self._device][self._id].location,
|
||||
verisure.CLIMATE_STATUS[self._id].location,
|
||||
"Temperature")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
# remove ° character
|
||||
return verisure.STATUS[self._device][self._id].temperature[:-1]
|
||||
return verisure.CLIMATE_STATUS[self._id].temperature[:-1]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
@ -69,7 +68,7 @@ class VerisureThermometer(Entity):
|
||||
|
||||
def update(self):
|
||||
''' update sensor '''
|
||||
verisure.update()
|
||||
verisure.update_climate()
|
||||
|
||||
|
||||
class VerisureHygrometer(Entity):
|
||||
@ -77,20 +76,19 @@ class VerisureHygrometer(Entity):
|
||||
|
||||
def __init__(self, climate_status):
|
||||
self._id = climate_status.id
|
||||
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return '{} {}'.format(
|
||||
verisure.STATUS[self._device][self._id].location,
|
||||
verisure.CLIMATE_STATUS[self._id].location,
|
||||
"Humidity")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
# remove % character
|
||||
return verisure.STATUS[self._device][self._id].humidity[:-1]
|
||||
return verisure.CLIMATE_STATUS[self._id].humidity[:-1]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
@ -99,4 +97,4 @@ class VerisureHygrometer(Entity):
|
||||
|
||||
def update(self):
|
||||
''' update sensor '''
|
||||
verisure.update()
|
||||
verisure.update_climate()
|
||||
|
223
homeassistant/components/sensor/yr.py
Normal file
223
homeassistant/components/sensor/yr.py
Normal file
@ -0,0 +1,223 @@
|
||||
"""
|
||||
homeassistant.components.sensor.yr
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Yr.no weather service.
|
||||
|
||||
Configuration:
|
||||
|
||||
Will show a symbol for the current weather as default:
|
||||
sensor:
|
||||
platform: yr
|
||||
|
||||
Will show temperatue and wind direction:
|
||||
sensor:
|
||||
platform: yr
|
||||
monitored_conditions:
|
||||
- temperature
|
||||
- windDirection
|
||||
|
||||
Will show all available sensors:
|
||||
sensor:
|
||||
platform: yr
|
||||
monitored_conditions:
|
||||
- temperature
|
||||
- symbol
|
||||
- precipitation
|
||||
- windSpeed
|
||||
- pressure
|
||||
- windDirection
|
||||
- humidity
|
||||
- fog
|
||||
- cloudiness
|
||||
- lowClouds
|
||||
- mediumClouds
|
||||
- highClouds
|
||||
- dewpointTemperature
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import location, dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REQUIREMENTS = ['xmltodict']
|
||||
|
||||
# Sensor types are defined like so:
|
||||
SENSOR_TYPES = {
|
||||
'symbol': ['Symbol', ''],
|
||||
'precipitation': ['Condition', 'mm'],
|
||||
'temperature': ['Temperature', '°C'],
|
||||
'windSpeed': ['Wind speed', 'm/s'],
|
||||
'pressure': ['Pressure', 'hPa'],
|
||||
'windDirection': ['Wind direction', '°'],
|
||||
'humidity': ['Humidity', '%'],
|
||||
'fog': ['Fog', '%'],
|
||||
'cloudiness': ['Cloudiness', '%'],
|
||||
'lowClouds': ['Low clouds', '%'],
|
||||
'mediumClouds': ['Medium clouds', '%'],
|
||||
'highClouds': ['High clouds', '%'],
|
||||
'dewpointTemperature': ['Dewpoint temperature', '°C'],
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Get the yr.no sensor. """
|
||||
|
||||
if None in (hass.config.latitude, hass.config.longitude):
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
elevation = config.get('elevation')
|
||||
|
||||
if elevation is None:
|
||||
elevation = location.elevation(hass.config.latitude,
|
||||
hass.config.longitude)
|
||||
|
||||
coordinates = dict(lat=hass.config.latitude,
|
||||
lon=hass.config.longitude, msl=elevation)
|
||||
|
||||
weather = YrData(coordinates)
|
||||
|
||||
dev = []
|
||||
if 'monitored_conditions' in config:
|
||||
for variable in config['monitored_conditions']:
|
||||
if variable not in SENSOR_TYPES:
|
||||
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
||||
else:
|
||||
dev.append(YrSensor(variable, weather))
|
||||
|
||||
# add symbol as default sensor
|
||||
if len(dev) == 0:
|
||||
dev.append(YrSensor("symbol", weather))
|
||||
add_devices(dev)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class YrSensor(Entity):
|
||||
""" Implements an Yr.no sensor. """
|
||||
|
||||
def __init__(self, sensor_type, weather):
|
||||
self.client_name = 'yr'
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._weather = weather
|
||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||
self._update = None
|
||||
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '{} {}'.format(self.client_name, self._name)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns state attributes. """
|
||||
data = {
|
||||
'about': "Weather forecast from yr.no, delivered by the"
|
||||
" Norwegian Meteorological Institute and the NRK"
|
||||
}
|
||||
if self.type == 'symbol':
|
||||
symbol_nr = self._state
|
||||
data[ATTR_ENTITY_PICTURE] = \
|
||||
"http://api.met.no/weatherapi/weathericon/1.1/" \
|
||||
"?symbol={0};content_type=image/png".format(symbol_nr)
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data from yr.no and updates the states. """
|
||||
|
||||
now = dt_util.utcnow()
|
||||
# check if data should be updated
|
||||
if self._update is not None and now <= self._update:
|
||||
return
|
||||
|
||||
self._weather.update()
|
||||
|
||||
# find sensor
|
||||
for time_entry in self._weather.data['product']['time']:
|
||||
valid_from = dt_util.str_to_datetime(
|
||||
time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ")
|
||||
valid_to = dt_util.str_to_datetime(
|
||||
time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
loc_data = time_entry['location']
|
||||
|
||||
if self.type not in loc_data or now >= valid_to:
|
||||
continue
|
||||
|
||||
self._update = valid_to
|
||||
|
||||
if self.type == 'precipitation' and valid_from < now:
|
||||
self._state = loc_data[self.type]['@value']
|
||||
break
|
||||
elif self.type == 'symbol' and valid_from < now:
|
||||
self._state = loc_data[self.type]['@number']
|
||||
break
|
||||
elif self.type == ('temperature', 'pressure', 'humidity',
|
||||
'dewpointTemperature'):
|
||||
self._state = loc_data[self.type]['@value']
|
||||
break
|
||||
elif self.type == 'windSpeed':
|
||||
self._state = loc_data[self.type]['@mps']
|
||||
break
|
||||
elif self.type == 'windDirection':
|
||||
self._state = float(loc_data[self.type]['@deg'])
|
||||
break
|
||||
elif self.type in ('fog', 'cloudiness', 'lowClouds',
|
||||
'mediumClouds', 'highClouds'):
|
||||
self._state = loc_data[self.type]['@percent']
|
||||
break
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class YrData(object):
|
||||
""" Gets the latest data and updates the states. """
|
||||
|
||||
def __init__(self, coordinates):
|
||||
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
||||
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
||||
|
||||
self._nextrun = None
|
||||
self.data = {}
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data from yr.no """
|
||||
# check if new will be available
|
||||
if self._nextrun is not None and dt_util.utcnow() <= self._nextrun:
|
||||
return
|
||||
try:
|
||||
with requests.Session() as sess:
|
||||
response = sess.get(self._url)
|
||||
except requests.RequestException:
|
||||
return
|
||||
if response.status_code != 200:
|
||||
return
|
||||
data = response.text
|
||||
|
||||
import xmltodict
|
||||
self.data = xmltodict.parse(data)['weatherdata']
|
||||
model = self.data['meta']['model']
|
||||
if '@nextrun' not in model:
|
||||
model = model[0]
|
||||
self._nextrun = dt_util.str_to_datetime(model['@nextrun'],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
@ -8,10 +8,9 @@ https://home-assistant.io/components/sun/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import urllib
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util import location as location_util, dt as dt_util
|
||||
from homeassistant.helpers.event import (
|
||||
track_point_in_utc_time, track_utc_time_change)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -111,21 +110,13 @@ def setup(hass, config):
|
||||
platform_config = config.get(DOMAIN, {})
|
||||
|
||||
elevation = platform_config.get(CONF_ELEVATION)
|
||||
if elevation is None:
|
||||
elevation = location_util.elevation(latitude, longitude)
|
||||
|
||||
from astral import Location, GoogleGeocoder
|
||||
from astral import Location
|
||||
|
||||
location = Location(('', '', latitude, longitude, hass.config.time_zone,
|
||||
elevation or 0))
|
||||
|
||||
if elevation is None:
|
||||
google = GoogleGeocoder()
|
||||
try:
|
||||
google._get_elevation(location) # pylint: disable=protected-access
|
||||
_LOGGER.info(
|
||||
'Retrieved elevation from Google: %s', location.elevation)
|
||||
except urllib.error.URLError:
|
||||
# If no internet connection available etc.
|
||||
pass
|
||||
elevation))
|
||||
|
||||
sun = Sun(hass, location)
|
||||
sun.point_in_time_listener(dt_util.utcnow())
|
||||
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.components import (
|
||||
group, discovery, wink, isy994, verisure, zwave)
|
||||
group, discovery, wink, isy994, verisure, zwave, tellduslive)
|
||||
|
||||
DOMAIN = 'switch'
|
||||
SCAN_INTERVAL = 30
|
||||
@ -40,6 +40,7 @@ DISCOVERY_PLATFORMS = {
|
||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||
verisure.DISCOVER_SWITCHES: 'verisure',
|
||||
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
|
||||
}
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
|
@ -10,6 +10,8 @@ import logging
|
||||
import subprocess
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.util import template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -24,20 +26,30 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
for dev_name, properties in switches.items():
|
||||
devices.append(
|
||||
CommandSwitch(
|
||||
hass,
|
||||
properties.get('name', dev_name),
|
||||
properties.get('oncmd', 'true'),
|
||||
properties.get('offcmd', 'true')))
|
||||
properties.get('offcmd', 'true'),
|
||||
properties.get('statecmd', False),
|
||||
properties.get(CONF_VALUE_TEMPLATE, False)))
|
||||
|
||||
add_devices_callback(devices)
|
||||
|
||||
|
||||
class CommandSwitch(SwitchDevice):
|
||||
""" Represents a switch that can be togggled using shell commands. """
|
||||
def __init__(self, name, command_on, command_off):
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, hass, name, command_on, command_off,
|
||||
command_state, value_template):
|
||||
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._state = False
|
||||
self._command_on = command_on
|
||||
self._command_off = command_off
|
||||
self._command_state = command_state
|
||||
self._value_template = value_template
|
||||
|
||||
@staticmethod
|
||||
def _switch(command):
|
||||
@ -51,10 +63,27 @@ class CommandSwitch(SwitchDevice):
|
||||
|
||||
return success
|
||||
|
||||
@staticmethod
|
||||
def _query_state_value(command):
|
||||
""" Execute state command for return value. """
|
||||
_LOGGER.info('Running state command: %s', command)
|
||||
|
||||
try:
|
||||
return_value = subprocess.check_output(command, shell=True)
|
||||
return return_value.strip().decode('utf-8')
|
||||
except subprocess.CalledProcessError:
|
||||
_LOGGER.error('Command failed: %s', command)
|
||||
|
||||
@staticmethod
|
||||
def _query_state_code(command):
|
||||
""" Execute state command for return code. """
|
||||
_LOGGER.info('Running state command: %s', command)
|
||||
return subprocess.call(command, shell=True) == 0
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed. """
|
||||
return False
|
||||
""" Only poll if we have statecmd. """
|
||||
return self._command_state is not None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -66,14 +95,34 @@ class CommandSwitch(SwitchDevice):
|
||||
""" True if device is on. """
|
||||
return self._state
|
||||
|
||||
def _query_state(self):
|
||||
""" Query for state. """
|
||||
if not self._command_state:
|
||||
_LOGGER.error('No state command specified')
|
||||
return
|
||||
if self._value_template:
|
||||
return CommandSwitch._query_state_value(self._command_state)
|
||||
return CommandSwitch._query_state_code(self._command_state)
|
||||
|
||||
def update(self):
|
||||
""" Update device state. """
|
||||
if self._command_state:
|
||||
payload = str(self._query_state())
|
||||
if self._value_template:
|
||||
payload = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, payload)
|
||||
self._state = (payload.lower() == "true")
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turn the device on. """
|
||||
if CommandSwitch._switch(self._command_on):
|
||||
if (CommandSwitch._switch(self._command_on) and
|
||||
not self._command_state):
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turn the device off. """
|
||||
if CommandSwitch._switch(self._command_off):
|
||||
if (CommandSwitch._switch(self._command_off) and
|
||||
not self._command_state):
|
||||
self._state = False
|
||||
self.update_ha_state()
|
||||
self.update_ha_state()
|
||||
|
@ -18,7 +18,7 @@ DEFAULT_BODY_ON = "ON"
|
||||
DEFAULT_BODY_OFF = "OFF"
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=unused-argument,
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Get REST switch. """
|
||||
|
||||
@ -32,11 +32,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
requests.get(resource, timeout=10)
|
||||
except requests.exceptions.MissingSchema:
|
||||
_LOGGER.error("Missing resource or schema in configuration. "
|
||||
"Add http:// to your URL.")
|
||||
"Add http:// or https:// to your URL")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint. "
|
||||
"Please check the IP address in the configuration file.")
|
||||
_LOGGER.error("No route to resource/endpoint: %s", resource)
|
||||
return False
|
||||
|
||||
add_devices_callback([RestSwitch(
|
||||
|
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()
|
@ -13,11 +13,13 @@ import homeassistant.util.dt as dt_util
|
||||
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME)
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_TRIPPED,
|
||||
ATTR_ARMED,
|
||||
ATTR_LAST_TRIP_TIME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
|
||||
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
|
||||
'#python-vera==0.1.1']
|
||||
REQUIREMENTS = ['pyvera==0.2.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -37,7 +39,16 @@ def get_devices(hass, config):
|
||||
|
||||
device_data = config.get('device_data', {})
|
||||
|
||||
vera_controller = veraApi.VeraController(base_url)
|
||||
vera_controller, created = veraApi.init_controller(base_url)
|
||||
|
||||
if created:
|
||||
def stop_subscription(event):
|
||||
""" Shutdown Vera subscriptions and subscription thread on exit"""
|
||||
_LOGGER.info("Shutting down subscriptions.")
|
||||
vera_controller.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
|
||||
|
||||
devices = []
|
||||
try:
|
||||
devices = vera_controller.get_devices([
|
||||
@ -53,7 +64,8 @@ def get_devices(hass, config):
|
||||
exclude = extra_data.get('exclude', False)
|
||||
|
||||
if exclude is not True:
|
||||
vera_switches.append(VeraSwitch(device, extra_data))
|
||||
vera_switches.append(
|
||||
VeraSwitch(device, vera_controller, extra_data))
|
||||
|
||||
return vera_switches
|
||||
|
||||
@ -66,9 +78,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class VeraSwitch(ToggleEntity):
|
||||
""" Represents a Vera Switch. """
|
||||
|
||||
def __init__(self, vera_device, extra_data=None):
|
||||
def __init__(self, vera_device, controller, extra_data=None):
|
||||
self.vera_device = vera_device
|
||||
self.extra_data = extra_data
|
||||
self.controller = controller
|
||||
if self.extra_data and self.extra_data.get('name'):
|
||||
self._name = self.extra_data.get('name')
|
||||
else:
|
||||
@ -77,6 +90,16 @@ class VeraSwitch(ToggleEntity):
|
||||
# for debouncing status check after command is sent
|
||||
self.last_command_send = 0
|
||||
|
||||
self.controller.register(vera_device)
|
||||
self.controller.on(
|
||||
vera_device, self._update_callback)
|
||||
|
||||
def _update_callback(self, _device):
|
||||
""" Called by the vera device callback to update state. """
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s', self.name)
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Get the mame of the switch. """
|
||||
@ -118,6 +141,11 @@ class VeraSwitch(ToggleEntity):
|
||||
self.vera_device.switch_off()
|
||||
self.is_on_status = False
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if device is on. """
|
||||
|
@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
switches.extend([
|
||||
VerisureSmartplug(value)
|
||||
for value in verisure.get_smartplug_status().values()
|
||||
for value in verisure.SMARTPLUG_STATUS.values()
|
||||
if verisure.SHOW_SMARTPLUGS
|
||||
])
|
||||
|
||||
@ -36,31 +36,29 @@ class VerisureSmartplug(SwitchDevice):
|
||||
""" Represents a Verisure smartplug. """
|
||||
def __init__(self, smartplug_status):
|
||||
self._id = smartplug_status.id
|
||||
self.status_on = verisure.MY_PAGES.SMARTPLUG_ON
|
||||
self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Get the name (location) of the smartplug. """
|
||||
return verisure.get_smartplug_status()[self._id].location
|
||||
return verisure.SMARTPLUG_STATUS[self._id].location
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" Returns True if on """
|
||||
plug_status = verisure.get_smartplug_status()[self._id].status
|
||||
return plug_status == self.status_on
|
||||
plug_status = verisure.SMARTPLUG_STATUS[self._id].status
|
||||
return plug_status == 'on'
|
||||
|
||||
def turn_on(self):
|
||||
""" Set smartplug status on. """
|
||||
verisure.MY_PAGES.set_smartplug_status(
|
||||
self._id,
|
||||
self.status_on)
|
||||
verisure.MY_PAGES.smartplug.set(self._id, 'on')
|
||||
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'on')
|
||||
verisure.update_smartplug()
|
||||
|
||||
def turn_off(self):
|
||||
""" Set smartplug status off. """
|
||||
verisure.MY_PAGES.set_smartplug_status(
|
||||
self._id,
|
||||
self.status_off)
|
||||
verisure.MY_PAGES.smartplug.set(self._id, 'off')
|
||||
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'off')
|
||||
verisure.update_smartplug()
|
||||
|
||||
def update(self):
|
||||
verisure.update()
|
||||
verisure.update_smartplug()
|
||||
|
@ -9,11 +9,14 @@ https://home-assistant.io/components/switch.wemo/
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['pywemo==0.3.3']
|
||||
REQUIREMENTS = ['pywemo==0.3.7']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_WEMO_SUBSCRIPTION_REGISTRY = None
|
||||
|
||||
|
||||
# pylint: disable=unused-argument, too-many-function-args
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
@ -21,6 +24,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
import pywemo
|
||||
import pywemo.discovery as discovery
|
||||
|
||||
global _WEMO_SUBSCRIPTION_REGISTRY
|
||||
if _WEMO_SUBSCRIPTION_REGISTRY is None:
|
||||
_WEMO_SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry()
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.start()
|
||||
|
||||
def stop_wemo(event):
|
||||
""" Shutdown Wemo subscriptions and subscription thread on exit"""
|
||||
_LOGGER.info("Shutting down subscriptions.")
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)
|
||||
|
||||
if discovery_info is not None:
|
||||
location = discovery_info[2]
|
||||
mac = discovery_info[3]
|
||||
@ -47,6 +62,23 @@ class WemoSwitch(SwitchDevice):
|
||||
self.insight_params = None
|
||||
self.maker_params = None
|
||||
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.register(wemo)
|
||||
_WEMO_SUBSCRIPTION_REGISTRY.on(
|
||||
wemo, None, self._update_callback)
|
||||
|
||||
def _update_callback(self, _device, _params):
|
||||
""" Called by the wemo device callback to update state. """
|
||||
_LOGGER.info(
|
||||
'Subscription update for %s, sevice=%s',
|
||||
self.name, _device)
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling should be needed with subscriptions """
|
||||
# but leave in for initial version in case of issues.
|
||||
return True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this WeMo switch """
|
||||
|
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/
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant import bootstrap
|
||||
@ -26,15 +28,14 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
||||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||
|
||||
DEPENDENCIES = ['alarm_control_panel']
|
||||
REQUIREMENTS = [
|
||||
'https://github.com/persandstrom/python-verisure/archive/'
|
||||
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
|
||||
]
|
||||
REQUIREMENTS = ['vsure==0.4.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MY_PAGES = None
|
||||
STATUS = {}
|
||||
ALARM_STATUS = {}
|
||||
SMARTPLUG_STATUS = {}
|
||||
CLIMATE_STATUS = {}
|
||||
|
||||
VERISURE_LOGIN_ERROR = None
|
||||
VERISURE_ERROR = None
|
||||
@ -47,7 +48,7 @@ SHOW_SMARTPLUGS = True
|
||||
# if wrong password was given don't try again
|
||||
WRONG_PASSWORD_GIVEN = False
|
||||
|
||||
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5)
|
||||
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=1)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
@ -60,10 +61,6 @@ def setup(hass, config):
|
||||
|
||||
from verisure import MyPages, LoginError, Error
|
||||
|
||||
STATUS[MyPages.DEVICE_ALARM] = {}
|
||||
STATUS[MyPages.DEVICE_CLIMATE] = {}
|
||||
STATUS[MyPages.DEVICE_SMARTPLUG] = {}
|
||||
|
||||
global SHOW_THERMOMETERS, SHOW_HYGROMETERS, SHOW_ALARM, SHOW_SMARTPLUGS
|
||||
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
||||
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1'))
|
||||
@ -84,7 +81,9 @@ def setup(hass, config):
|
||||
_LOGGER.error('Could not log in to verisure mypages, %s', ex)
|
||||
return False
|
||||
|
||||
update()
|
||||
update_alarm()
|
||||
update_climate()
|
||||
update_smartplug()
|
||||
|
||||
# Load components for the devices in the ISY controller that we support
|
||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||
@ -101,24 +100,10 @@ def setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
def get_alarm_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_ALARM]
|
||||
|
||||
|
||||
def get_climate_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_CLIMATE]
|
||||
|
||||
|
||||
def get_smartplug_status():
|
||||
""" Return a list of status overviews for alarm components. """
|
||||
return STATUS[MY_PAGES.DEVICE_SMARTPLUG]
|
||||
|
||||
|
||||
def reconnect():
|
||||
""" Reconnect to verisure mypages. """
|
||||
try:
|
||||
time.sleep(1)
|
||||
MY_PAGES.login()
|
||||
except VERISURE_LOGIN_ERROR as ex:
|
||||
_LOGGER.error("Could not login to Verisure mypages, %s", ex)
|
||||
@ -129,19 +114,31 @@ def reconnect():
|
||||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update():
|
||||
def update_alarm():
|
||||
""" Updates the status of alarms. """
|
||||
update_component(MY_PAGES.alarm.get, ALARM_STATUS)
|
||||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update_climate():
|
||||
""" Updates the status of climate sensors. """
|
||||
update_component(MY_PAGES.climate.get, CLIMATE_STATUS)
|
||||
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
|
||||
def update_smartplug():
|
||||
""" Updates the status of smartplugs. """
|
||||
update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS)
|
||||
|
||||
|
||||
def update_component(get_function, status):
|
||||
""" Updates the status of verisure components. """
|
||||
if WRONG_PASSWORD_GIVEN:
|
||||
_LOGGER.error('Wrong password')
|
||||
return
|
||||
|
||||
try:
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM):
|
||||
STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE):
|
||||
STATUS[MY_PAGES.DEVICE_CLIMATE][overview.id] = overview
|
||||
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG):
|
||||
STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview
|
||||
except ConnectionError as ex:
|
||||
for overview in get_function():
|
||||
status[overview.id] = overview
|
||||
except (ConnectionError, VERISURE_ERROR) as ex:
|
||||
_LOGGER.error('Caught connection error %s, tries to reconnect', ex)
|
||||
reconnect()
|
||||
|
@ -1,7 +1,7 @@
|
||||
# coding: utf-8
|
||||
""" Constants used by Home Assistant components. """
|
||||
|
||||
__version__ = "0.10.0.dev0"
|
||||
__version__ = "0.11.0.dev0"
|
||||
|
||||
# Can be used to specify a catch all when registering state or event listeners.
|
||||
MATCH_ALL = '*'
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""
|
||||
homeassistant
|
||||
~~~~~~~~~~~~~
|
||||
Core components of Home Assistant.
|
||||
|
||||
Home Assistant is a Home Automation framework for observing the state
|
||||
of entities and react to changes.
|
||||
@ -53,9 +52,10 @@ _MockHA = namedtuple("MockHomeAssistant", ['bus'])
|
||||
|
||||
|
||||
class HomeAssistant(object):
|
||||
""" Core class to route all communication to right components. """
|
||||
"""Root object of the Home Assistant home automation."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize new Home Assistant object."""
|
||||
self.pool = pool = create_worker_pool()
|
||||
self.bus = EventBus(pool)
|
||||
self.services = ServiceRegistry(self.bus, pool)
|
||||
@ -63,7 +63,7 @@ class HomeAssistant(object):
|
||||
self.config = Config()
|
||||
|
||||
def start(self):
|
||||
""" Start home assistant. """
|
||||
"""Start home assistant."""
|
||||
_LOGGER.info(
|
||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||
|
||||
@ -71,12 +71,11 @@ class HomeAssistant(object):
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
def block_till_stopped(self):
|
||||
""" Will register service homeassistant/stop and
|
||||
will block until called. """
|
||||
"""Register service homeassistant/stop and will block until called."""
|
||||
request_shutdown = threading.Event()
|
||||
|
||||
def stop_homeassistant(*args):
|
||||
""" Stops Home Assistant. """
|
||||
"""Stop Home Assistant."""
|
||||
request_shutdown.set()
|
||||
|
||||
self.services.register(
|
||||
@ -98,7 +97,7 @@ class HomeAssistant(object):
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
""" Stops Home Assistant and shuts down all threads. """
|
||||
"""Stop Home Assistant and shuts down all threads."""
|
||||
_LOGGER.info("Stopping")
|
||||
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||
@ -150,8 +149,7 @@ class HomeAssistant(object):
|
||||
|
||||
|
||||
class JobPriority(util.OrderedEnum):
|
||||
""" Provides priorities for bus events. """
|
||||
# pylint: disable=no-init,too-few-public-methods
|
||||
"""Provides job priorities for event bus jobs."""
|
||||
|
||||
EVENT_CALLBACK = 0
|
||||
EVENT_SERVICE = 1
|
||||
@ -161,7 +159,7 @@ class JobPriority(util.OrderedEnum):
|
||||
|
||||
@staticmethod
|
||||
def from_event_type(event_type):
|
||||
""" Returns a priority based on event type. """
|
||||
"""Return a priority based on event type."""
|
||||
if event_type == EVENT_TIME_CHANGED:
|
||||
return JobPriority.EVENT_TIME
|
||||
elif event_type == EVENT_STATE_CHANGED:
|
||||
@ -175,8 +173,7 @@ class JobPriority(util.OrderedEnum):
|
||||
|
||||
|
||||
class EventOrigin(enum.Enum):
|
||||
""" Distinguish between origin of event. """
|
||||
# pylint: disable=no-init,too-few-public-methods
|
||||
"""Represents origin of an event."""
|
||||
|
||||
local = "LOCAL"
|
||||
remote = "REMOTE"
|
||||
@ -185,14 +182,15 @@ class EventOrigin(enum.Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Event(object):
|
||||
""" Represents an event within the Bus. """
|
||||
# pylint: disable=too-few-public-methods
|
||||
"""Represents an event within the Bus."""
|
||||
|
||||
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
|
||||
|
||||
def __init__(self, event_type, data=None, origin=EventOrigin.local,
|
||||
time_fired=None):
|
||||
"""Initialize a new event."""
|
||||
self.event_type = event_type
|
||||
self.data = data or {}
|
||||
self.origin = origin
|
||||
@ -200,7 +198,7 @@ class Event(object):
|
||||
time_fired or dt_util.utcnow())
|
||||
|
||||
def as_dict(self):
|
||||
""" Returns a dict representation of this Event. """
|
||||
"""Create a dict representation of this Event."""
|
||||
return {
|
||||
'event_type': self.event_type,
|
||||
'data': dict(self.data),
|
||||
@ -227,26 +225,23 @@ class Event(object):
|
||||
|
||||
|
||||
class EventBus(object):
|
||||
""" Class that allows different components to communicate via services
|
||||
and events.
|
||||
"""
|
||||
"""Allows firing of and listening for events."""
|
||||
|
||||
def __init__(self, pool=None):
|
||||
"""Initialize a new event bus."""
|
||||
self._listeners = {}
|
||||
self._lock = threading.Lock()
|
||||
self._pool = pool or create_worker_pool()
|
||||
|
||||
@property
|
||||
def listeners(self):
|
||||
""" Dict with events that is being listened for and the number
|
||||
of listeners.
|
||||
"""
|
||||
"""Dict with events and the number of listeners."""
|
||||
with self._lock:
|
||||
return {key: len(self._listeners[key])
|
||||
for key in self._listeners}
|
||||
|
||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
||||
""" Fire an event. """
|
||||
"""Fire an event."""
|
||||
if not self._pool.running:
|
||||
raise HomeAssistantError('Home Assistant has shut down.')
|
||||
|
||||
@ -271,7 +266,7 @@ class EventBus(object):
|
||||
self._pool.add_job(job_priority, (func, event))
|
||||
|
||||
def listen(self, event_type, listener):
|
||||
""" Listen for all events or events of a specific type.
|
||||
"""Listen for all events or events of a specific type.
|
||||
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
@ -283,7 +278,7 @@ class EventBus(object):
|
||||
self._listeners[event_type] = [listener]
|
||||
|
||||
def listen_once(self, event_type, listener):
|
||||
""" Listen once for event of a specific type.
|
||||
"""Listen once for event of a specific type.
|
||||
|
||||
To listen to all events specify the constant ``MATCH_ALL``
|
||||
as event_type.
|
||||
@ -292,7 +287,7 @@ class EventBus(object):
|
||||
"""
|
||||
@ft.wraps(listener)
|
||||
def onetime_listener(event):
|
||||
""" Removes listener from eventbus and then fires listener. """
|
||||
"""Remove listener from eventbus and then fires listener."""
|
||||
if hasattr(onetime_listener, 'run'):
|
||||
return
|
||||
# Set variable so that we will never run twice.
|
||||
@ -311,7 +306,7 @@ class EventBus(object):
|
||||
return onetime_listener
|
||||
|
||||
def remove_listener(self, event_type, listener):
|
||||
""" Removes a listener of a specific event_type. """
|
||||
"""Remove a listener of a specific event_type."""
|
||||
with self._lock:
|
||||
try:
|
||||
self._listeners[event_type].remove(listener)
|
||||
@ -343,6 +338,7 @@ class State(object):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
||||
last_updated=None):
|
||||
"""Initialize a new state."""
|
||||
if not ENTITY_ID_PATTERN.match(entity_id):
|
||||
raise InvalidEntityFormatError((
|
||||
"Invalid entity id encountered: {}. "
|
||||
@ -363,31 +359,33 @@ class State(object):
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
""" Returns domain of this state. """
|
||||
"""Domain of this state."""
|
||||
return util.split_entity_id(self.entity_id)[0]
|
||||
|
||||
@property
|
||||
def object_id(self):
|
||||
""" Returns object_id of this state. """
|
||||
"""Object id of this state."""
|
||||
return util.split_entity_id(self.entity_id)[1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Name to represent this state. """
|
||||
"""Name of this state."""
|
||||
return (
|
||||
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
||||
self.object_id.replace('_', ' '))
|
||||
|
||||
def copy(self):
|
||||
""" Creates a copy of itself. """
|
||||
"""Return a copy of the state."""
|
||||
return State(self.entity_id, self.state,
|
||||
dict(self.attributes), self.last_changed,
|
||||
self.last_updated)
|
||||
|
||||
def as_dict(self):
|
||||
""" Converts State to a dict to be used within JSON.
|
||||
Ensures: state == State.from_dict(state.as_dict()) """
|
||||
"""Return a dict representation of the State.
|
||||
|
||||
To be used for JSON serialization.
|
||||
Ensures: state == State.from_dict(state.as_dict())
|
||||
"""
|
||||
return {'entity_id': self.entity_id,
|
||||
'state': self.state,
|
||||
'attributes': self.attributes,
|
||||
@ -396,11 +394,11 @@ class State(object):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, json_dict):
|
||||
""" Static method to create a state from a dict.
|
||||
Ensures: state == State.from_json_dict(state.to_json_dict()) """
|
||||
"""Initialize a state from a dict.
|
||||
|
||||
if not (json_dict and
|
||||
'entity_id' in json_dict and
|
||||
Ensures: state == State.from_json_dict(state.to_json_dict())
|
||||
"""
|
||||
if not (json_dict and 'entity_id' in json_dict and
|
||||
'state' in json_dict):
|
||||
return None
|
||||
|
||||
@ -433,15 +431,16 @@ class State(object):
|
||||
|
||||
|
||||
class StateMachine(object):
|
||||
""" Helper class that tracks the state of different entities. """
|
||||
"""Helper class that tracks the state of different entities."""
|
||||
|
||||
def __init__(self, bus):
|
||||
"""Initialize state machine."""
|
||||
self._states = {}
|
||||
self._bus = bus
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def entity_ids(self, domain_filter=None):
|
||||
""" List of entity ids that are being tracked. """
|
||||
"""List of entity ids that are being tracked."""
|
||||
if domain_filter is None:
|
||||
return list(self._states.keys())
|
||||
|
||||
@ -451,35 +450,43 @@ class StateMachine(object):
|
||||
if state.domain == domain_filter]
|
||||
|
||||
def all(self):
|
||||
""" Returns a list of all states. """
|
||||
"""Create a list of all states."""
|
||||
with self._lock:
|
||||
return [state.copy() for state in self._states.values()]
|
||||
|
||||
def get(self, entity_id):
|
||||
""" Returns the state of the specified entity. """
|
||||
"""Retrieve state of entity_id or None if not found."""
|
||||
state = self._states.get(entity_id.lower())
|
||||
|
||||
# Make a copy so people won't mutate the state
|
||||
return state.copy() if state else None
|
||||
|
||||
def is_state(self, entity_id, state):
|
||||
""" Returns True if entity exists and is specified state. """
|
||||
"""Test if entity exists and is specified state."""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
return (entity_id in self._states and
|
||||
self._states[entity_id].state == state)
|
||||
|
||||
def remove(self, entity_id):
|
||||
""" Removes an entity from the state machine.
|
||||
def is_state_attr(self, entity_id, name, value):
|
||||
"""Test if entity exists and has a state attribute set to value."""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
Returns boolean to indicate if an entity was removed. """
|
||||
return (entity_id in self._states and
|
||||
self._states[entity_id].attributes.get(name, None) == value)
|
||||
|
||||
def remove(self, entity_id):
|
||||
"""Remove the state of an entity.
|
||||
|
||||
Returns boolean to indicate if an entity was removed.
|
||||
"""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
with self._lock:
|
||||
return self._states.pop(entity_id, None) is not None
|
||||
|
||||
def set(self, entity_id, new_state, attributes=None):
|
||||
""" Set the state of an entity, add entity if it does not exist.
|
||||
"""Set the state of an entity, add entity if it does not exist.
|
||||
|
||||
Attributes is an optional dict to specify attributes of this state.
|
||||
|
||||
@ -514,9 +521,7 @@ class StateMachine(object):
|
||||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||
|
||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
||||
"""
|
||||
DEPRECATED AS OF 8/4/2015
|
||||
"""
|
||||
"""DEPRECATED AS OF 8/4/2015."""
|
||||
_LOGGER.warning(
|
||||
'hass.states.track_change is deprecated. '
|
||||
'Use homeassistant.helpers.event.track_state_change instead.')
|
||||
@ -527,33 +532,36 @@ class StateMachine(object):
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Service(object):
|
||||
""" Represents a service. """
|
||||
"""Represents a callable service."""
|
||||
|
||||
__slots__ = ['func', 'description', 'fields']
|
||||
|
||||
def __init__(self, func, description, fields):
|
||||
"""Initialize a service."""
|
||||
self.func = func
|
||||
self.description = description or ''
|
||||
self.fields = fields or {}
|
||||
|
||||
def as_dict(self):
|
||||
""" Return dictionary representation of this service. """
|
||||
"""Return dictionary representation of this service."""
|
||||
return {
|
||||
'description': self.description,
|
||||
'fields': self.fields,
|
||||
}
|
||||
|
||||
def __call__(self, call):
|
||||
"""Execute the service."""
|
||||
self.func(call)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ServiceCall(object):
|
||||
""" Represents a call to a service. """
|
||||
"""Represents a call to a service."""
|
||||
|
||||
__slots__ = ['domain', 'service', 'data']
|
||||
|
||||
def __init__(self, domain, service, data=None):
|
||||
"""Initialize a service call."""
|
||||
self.domain = domain
|
||||
self.service = service
|
||||
self.data = data or {}
|
||||
@ -567,9 +575,10 @@ class ServiceCall(object):
|
||||
|
||||
|
||||
class ServiceRegistry(object):
|
||||
""" Offers services over the eventbus. """
|
||||
"""Offers services over the eventbus."""
|
||||
|
||||
def __init__(self, bus, pool=None):
|
||||
"""Initialize a service registry."""
|
||||
self._services = {}
|
||||
self._lock = threading.Lock()
|
||||
self._pool = pool or create_worker_pool()
|
||||
@ -579,14 +588,14 @@ class ServiceRegistry(object):
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
""" Dict with per domain a list of available services. """
|
||||
"""Dict with per domain a list of available services."""
|
||||
with self._lock:
|
||||
return {domain: {key: value.as_dict() for key, value
|
||||
in self._services[domain].items()}
|
||||
for domain in self._services}
|
||||
|
||||
def has_service(self, domain, service):
|
||||
""" Returns True if specified service exists. """
|
||||
"""Test if specified service exists."""
|
||||
return service in self._services.get(domain, [])
|
||||
|
||||
def register(self, domain, service, service_func, description=None):
|
||||
@ -611,7 +620,8 @@ class ServiceRegistry(object):
|
||||
|
||||
def call(self, domain, service, service_data=None, blocking=False):
|
||||
"""
|
||||
Calls specified service.
|
||||
Call a service.
|
||||
|
||||
Specify blocking=True to wait till service is executed.
|
||||
Waits a maximum of SERVICE_CALL_LIMIT.
|
||||
|
||||
@ -635,10 +645,7 @@ class ServiceRegistry(object):
|
||||
executed_event = threading.Event()
|
||||
|
||||
def service_executed(call):
|
||||
"""
|
||||
Called when a service is executed.
|
||||
Will set the event if matches our service call.
|
||||
"""
|
||||
"""Callback method that is called when service is executed."""
|
||||
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
|
||||
executed_event.set()
|
||||
|
||||
@ -653,7 +660,7 @@ class ServiceRegistry(object):
|
||||
return success
|
||||
|
||||
def _event_to_service_call(self, event):
|
||||
""" Calls a service from an event. """
|
||||
"""Callback for SERVICE_CALLED events from the event bus."""
|
||||
service_data = dict(event.data)
|
||||
domain = service_data.pop(ATTR_DOMAIN, None)
|
||||
service = service_data.pop(ATTR_SERVICE, None)
|
||||
@ -670,7 +677,7 @@ class ServiceRegistry(object):
|
||||
(service_handler, service_call)))
|
||||
|
||||
def _execute_service(self, service_and_call):
|
||||
""" Executes a service and fires a SERVICE_EXECUTED event. """
|
||||
"""Execute a service and fires a SERVICE_EXECUTED event."""
|
||||
service, call = service_and_call
|
||||
service(call)
|
||||
|
||||
@ -680,16 +687,17 @@ class ServiceRegistry(object):
|
||||
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
|
||||
|
||||
def _generate_unique_id(self):
|
||||
""" Generates a unique service call id. """
|
||||
"""Generate a unique service call id."""
|
||||
self._cur_id += 1
|
||||
return "{}-{}".format(id(self), self._cur_id)
|
||||
|
||||
|
||||
class Config(object):
|
||||
""" Configuration settings for Home Assistant. """
|
||||
"""Configuration settings for Home Assistant."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self):
|
||||
"""Initialize a new config object."""
|
||||
self.latitude = None
|
||||
self.longitude = None
|
||||
self.temperature_unit = None
|
||||
@ -709,15 +717,15 @@ class Config(object):
|
||||
self.config_dir = get_default_config_dir()
|
||||
|
||||
def distance(self, lat, lon):
|
||||
""" Calculate distance from Home Assistant in meters. """
|
||||
"""Calculate distance from Home Assistant in meters."""
|
||||
return location.distance(self.latitude, self.longitude, lat, lon)
|
||||
|
||||
def path(self, *path):
|
||||
""" Returns path to the file within the config dir. """
|
||||
"""Generate path to the file within the config dir."""
|
||||
return os.path.join(self.config_dir, *path)
|
||||
|
||||
def temperature(self, value, unit):
|
||||
""" Converts temperature to user preferred unit if set. """
|
||||
"""Convert temperature to user preferred unit if set."""
|
||||
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
|
||||
self.temperature_unit and unit != self.temperature_unit):
|
||||
return value, unit
|
||||
@ -732,7 +740,7 @@ class Config(object):
|
||||
self.temperature_unit)
|
||||
|
||||
def as_dict(self):
|
||||
""" Converts config to a dictionary. """
|
||||
"""Create a dict representation of this dict."""
|
||||
time_zone = self.time_zone or dt_util.UTC
|
||||
|
||||
return {
|
||||
@ -747,7 +755,7 @@ class Config(object):
|
||||
|
||||
|
||||
def create_timer(hass, interval=TIMER_INTERVAL):
|
||||
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
|
||||
"""Create a timer that will start on HOMEASSISTANT_START."""
|
||||
# We want to be able to fire every time a minute starts (seconds=0).
|
||||
# We want this so other modules can use that to make sure they fire
|
||||
# every minute.
|
||||
@ -810,12 +818,12 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
||||
|
||||
|
||||
def create_worker_pool(worker_count=None):
|
||||
""" Creates a worker pool to be used. """
|
||||
"""Create a worker pool."""
|
||||
if worker_count is None:
|
||||
worker_count = MIN_WORKER_THREAD
|
||||
|
||||
def job_handler(job):
|
||||
""" Called whenever a job is available to do. """
|
||||
"""Called whenever a job is available to do."""
|
||||
try:
|
||||
func, arg = job
|
||||
func(arg)
|
||||
@ -825,8 +833,7 @@ def create_worker_pool(worker_count=None):
|
||||
_LOGGER.exception("BusHandler:Exception doing job")
|
||||
|
||||
def busy_callback(worker_count, current_jobs, pending_jobs_count):
|
||||
""" Callback to be called when the pool queue gets too big. """
|
||||
|
||||
"""Callback to be called when the pool queue gets too big."""
|
||||
_LOGGER.warning(
|
||||
"WorkerPool:All %d threads are busy and %d jobs pending",
|
||||
worker_count, pending_jobs_count)
|
||||
|
@ -113,12 +113,16 @@ class EntityComponent(object):
|
||||
|
||||
def _update_entity_states(self, now):
|
||||
""" Update the states of all the entities. """
|
||||
with self.lock:
|
||||
# We copy the entities because new entities might be detected
|
||||
# during state update causing deadlocks.
|
||||
entities = list(entity for entity in self.entities.values()
|
||||
if entity.should_poll)
|
||||
|
||||
self.logger.info("Updating %s entities", self.domain)
|
||||
|
||||
with self.lock:
|
||||
for entity in self.entities.values():
|
||||
if entity.should_poll:
|
||||
entity.update_ha_state(True)
|
||||
for entity in entities:
|
||||
entity.update_ha_state(True)
|
||||
|
||||
def _entity_discovered(self, service, info):
|
||||
""" Called when a entity is discovered. """
|
||||
|
@ -53,8 +53,12 @@ def color_xy_brightness_to_RGB(vX, vY, brightness):
|
||||
return (0, 0, 0)
|
||||
|
||||
Y = brightness
|
||||
X = (Y / vY) * vX
|
||||
Z = (Y / vY) * (1 - vX - vY)
|
||||
if vY != 0:
|
||||
X = (Y / vY) * vX
|
||||
Z = (Y / vY) * (1 - vX - vY)
|
||||
else:
|
||||
X = 0
|
||||
Z = 0
|
||||
|
||||
# Convert to RGB using Wide RGB D65 conversion.
|
||||
r = X * 1.612 - Y * 0.203 - Z * 0.302
|
||||
|
@ -108,14 +108,14 @@ def datetime_to_date_str(dattim):
|
||||
return dattim.strftime(DATE_STR_FORMAT)
|
||||
|
||||
|
||||
def str_to_datetime(dt_str):
|
||||
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
|
||||
""" Converts a string to a UTC datetime object.
|
||||
|
||||
@rtype: datetime
|
||||
"""
|
||||
try:
|
||||
return dt.datetime.strptime(
|
||||
dt_str, DATETIME_STR_FORMAT).replace(tzinfo=pytz.utc)
|
||||
dt_str, dt_format).replace(tzinfo=pytz.utc)
|
||||
except ValueError: # If dt_str did not match our format
|
||||
return None
|
||||
|
||||
|
@ -4,6 +4,8 @@ import collections
|
||||
import requests
|
||||
from vincenty import vincenty
|
||||
|
||||
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
||||
|
||||
|
||||
LocationInfo = collections.namedtuple(
|
||||
"LocationInfo",
|
||||
@ -34,3 +36,20 @@ def detect_location_info():
|
||||
def distance(lat1, lon1, lat2, lon2):
|
||||
""" Calculate the distance in meters between two points. """
|
||||
return vincenty((lat1, lon1), (lat2, lon2)) * 1000
|
||||
|
||||
|
||||
def elevation(latitude, longitude):
|
||||
""" Return elevation for given latitude and longitude. """
|
||||
|
||||
req = requests.get(ELEVATION_URL, params={
|
||||
'locations': '{},{}'.format(latitude, longitude),
|
||||
'sensor': 'false',
|
||||
})
|
||||
|
||||
if req.status_code != 200:
|
||||
return 0
|
||||
|
||||
try:
|
||||
return int(float(req.json()['results'][0]['elevation']))
|
||||
except (ValueError, KeyError):
|
||||
return 0
|
||||
|
@ -43,7 +43,8 @@ def render(hass, template, variables=None, **kwargs):
|
||||
try:
|
||||
return ENV.from_string(template, {
|
||||
'states': AllStates(hass),
|
||||
'is_state': hass.states.is_state
|
||||
'is_state': hass.states.is_state,
|
||||
'is_state_attr': hass.states.is_state_attr
|
||||
}).render(kwargs).strip()
|
||||
except jinja2.TemplateError as err:
|
||||
raise TemplateError(err)
|
||||
|
@ -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
|
@ -59,7 +59,7 @@ tellcore-py==1.1.2
|
||||
# homeassistant.components.light.vera
|
||||
# homeassistant.components.sensor.vera
|
||||
# homeassistant.components.switch.vera
|
||||
https://github.com/pavoni/home-assistant-vera-api/archive/efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip#python-vera==0.1.1
|
||||
pyvera==0.2.1
|
||||
|
||||
# homeassistant.components.wink
|
||||
# homeassistant.components.light.wink
|
||||
@ -157,6 +157,9 @@ transmissionrpc==0.11
|
||||
# homeassistant.components.sensor.twitch
|
||||
python-twitch==1.2.0
|
||||
|
||||
# homeassistant.components.sensor.yr
|
||||
xmltodict
|
||||
|
||||
# homeassistant.components.sun
|
||||
astral==0.8.1
|
||||
|
||||
@ -170,7 +173,10 @@ hikvision==0.4
|
||||
orvibo==1.1.0
|
||||
|
||||
# homeassistant.components.switch.wemo
|
||||
pywemo==0.3.3
|
||||
pywemo==0.3.7
|
||||
|
||||
# homeassistant.components.tellduslive
|
||||
tellive-py==0.5.2
|
||||
|
||||
# homeassistant.components.thermostat.heatmiser
|
||||
heatmiserV3==0.9.1
|
||||
@ -185,7 +191,7 @@ python-nest==2.6.0
|
||||
radiotherm==1.2
|
||||
|
||||
# homeassistant.components.verisure
|
||||
https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6
|
||||
vsure==0.4.3
|
||||
|
||||
# homeassistant.components.zwave
|
||||
pydispatcher==2.0.5
|
||||
|
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=$?
|
||||
|
||||
echo "Installing development dependencies.."
|
||||
python3 -m pip install flake8 pylint coveralls pytest pytest-cov
|
||||
python3 -m pip install -r requirements_test.txt
|
||||
|
||||
REQ_DEV_STATUS=$?
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ "$TRAVIS_PYTHON_VERSION" != "3.4" ]; then
|
||||
if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then
|
||||
NO_LINT=1
|
||||
fi
|
||||
|
||||
|
@ -115,7 +115,6 @@ def main():
|
||||
|
||||
if sys.argv[-1] == 'validate':
|
||||
if validate_file(data):
|
||||
print("requirements_all.txt is up to date.")
|
||||
sys.exit(0)
|
||||
print("******* ERROR")
|
||||
print("requirements_all.txt is not up to date")
|
||||
|
@ -3,13 +3,16 @@
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "Checking style with flake8..."
|
||||
tput setaf 1
|
||||
flake8 --exclude www_static homeassistant
|
||||
|
||||
FLAKE8_STATUS=$?
|
||||
tput sgr0
|
||||
|
||||
echo "Checking style with pylint..."
|
||||
tput setaf 1
|
||||
pylint homeassistant
|
||||
PYLINT_STATUS=$?
|
||||
tput sgr0
|
||||
|
||||
if [ $FLAKE8_STATUS -eq 0 ]
|
||||
then
|
||||
|
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',
|
||||
'pip>=7.0.0',
|
||||
'vincenty==0.1.3',
|
||||
'jinja2>=2.8'
|
||||
'jinja2>=2.8',
|
||||
]
|
||||
|
||||
setup(
|
||||
@ -33,6 +33,7 @@ setup(
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=REQUIRES,
|
||||
test_suite='tests',
|
||||
keywords=['home', 'automation'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
@ -46,5 +47,5 @@ setup(
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Topic :: Home Automation'
|
||||
]
|
||||
],
|
||||
)
|
||||
|
@ -0,0 +1,4 @@
|
||||
import betamax
|
||||
|
||||
with betamax.Betamax.configure() as config:
|
||||
config.cassette_library_dir = 'tests/cassettes'
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -139,3 +139,189 @@ class TestAutomationSun(unittest.TestCase):
|
||||
fire_time_changed(self.hass, trigger_time)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'before': 'sunrise',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_after(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before_with_offset(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'before': 'sunrise',
|
||||
'before_offset': '+1:00:00'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_after_with_offset(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
'after_offset': '+1:00:00'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_if_action_before_and_after_during(self):
|
||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
|
||||
sun.STATE_ATTR_NEXT_RISING: '10:00:00 16-09-2015',
|
||||
sun.STATE_ATTR_NEXT_SETTING: '15:00:00 16-09-2015',
|
||||
})
|
||||
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'condition': {
|
||||
'platform': 'sun',
|
||||
'after': 'sunrise',
|
||||
'before': 'sunset'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(self.calls))
|
||||
|
||||
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC)
|
||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
||||
return_value=now):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
205
tests/components/device_tracker/test_locative.py
Normal file
205
tests/components/device_tracker/test_locative.py
Normal file
@ -0,0 +1,205 @@
|
||||
"""
|
||||
tests.components.device_tracker.locative
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the locative device tracker component.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.components.device_tracker as device_tracker
|
||||
import homeassistant.components.http as http
|
||||
import homeassistant.components.zone as zone
|
||||
|
||||
SERVER_PORT = 8126
|
||||
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
||||
|
||||
hass = None
|
||||
|
||||
|
||||
def _url(data={}):
|
||||
""" Helper method to generate urls. """
|
||||
data = "&".join(["{}={}".format(name, value) for name, value in data.items()])
|
||||
return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data)
|
||||
|
||||
|
||||
@patch('homeassistant.components.http.util.get_local_ip',
|
||||
return_value='127.0.0.1')
|
||||
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
|
||||
""" Initalizes a Home Assistant server. """
|
||||
global hass
|
||||
|
||||
hass = ha.HomeAssistant()
|
||||
|
||||
# Set up server
|
||||
bootstrap.setup_component(hass, http.DOMAIN, {
|
||||
http.DOMAIN: {
|
||||
http.CONF_SERVER_PORT: SERVER_PORT
|
||||
}
|
||||
})
|
||||
|
||||
# Set up API
|
||||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
# Set up device tracker
|
||||
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
||||
device_tracker.DOMAIN: {
|
||||
'platform': 'locative'
|
||||
}
|
||||
})
|
||||
|
||||
hass.start()
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
""" Stops the Home Assistant server. """
|
||||
hass.stop()
|
||||
|
||||
# Stub out update_config or else Travis CI raises an exception
|
||||
@patch('homeassistant.components.device_tracker.update_config')
|
||||
class TestLocative(unittest.TestCase):
|
||||
""" Test Locative """
|
||||
|
||||
def test_missing_data(self, update_config):
|
||||
data = {
|
||||
'latitude': 1.0,
|
||||
'longitude': 1.1,
|
||||
'device': '123',
|
||||
'id': 'Home',
|
||||
'trigger': 'enter'
|
||||
}
|
||||
|
||||
# No data
|
||||
req = requests.get(_url({}))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# No latitude
|
||||
copy = data.copy()
|
||||
del copy['latitude']
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# No device
|
||||
copy = data.copy()
|
||||
del copy['device']
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# No location
|
||||
copy = data.copy()
|
||||
del copy['id']
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# No trigger
|
||||
copy = data.copy()
|
||||
del copy['trigger']
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# Test message
|
||||
copy = data.copy()
|
||||
copy['trigger'] = 'test'
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
# Unknown trigger
|
||||
copy = data.copy()
|
||||
copy['trigger'] = 'foobar'
|
||||
req = requests.get(_url(copy))
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
|
||||
def test_enter_and_exit(self, update_config):
|
||||
""" Test when there is a known zone """
|
||||
data = {
|
||||
'latitude': 40.7855,
|
||||
'longitude': -111.7367,
|
||||
'device': '123',
|
||||
'id': 'Home',
|
||||
'trigger': 'enter'
|
||||
}
|
||||
|
||||
# Enter the Home
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state
|
||||
self.assertEqual(state_name, 'home')
|
||||
|
||||
data['id'] = 'HOME'
|
||||
data['trigger'] = 'exit'
|
||||
|
||||
# Exit Home
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state
|
||||
self.assertEqual(state_name, 'not_home')
|
||||
|
||||
data['id'] = 'hOmE'
|
||||
data['trigger'] = 'enter'
|
||||
|
||||
# Enter Home again
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state
|
||||
self.assertEqual(state_name, 'home')
|
||||
|
||||
data['trigger'] = 'exit'
|
||||
|
||||
# Exit Home
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state
|
||||
self.assertEqual(state_name, 'not_home')
|
||||
|
||||
data['id'] = 'work'
|
||||
data['trigger'] = 'enter'
|
||||
|
||||
# Enter Work
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state
|
||||
self.assertEqual(state_name, 'work')
|
||||
|
||||
|
||||
def test_exit_after_enter(self, update_config):
|
||||
""" Test when an exit message comes after an enter message """
|
||||
|
||||
data = {
|
||||
'latitude': 40.7855,
|
||||
'longitude': -111.7367,
|
||||
'device': '123',
|
||||
'id': 'Home',
|
||||
'trigger': 'enter'
|
||||
}
|
||||
|
||||
# Enter Home
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
state = hass.states.get('{}.{}'.format('device_tracker', data['device']))
|
||||
self.assertEqual(state.state, 'home')
|
||||
|
||||
data['id'] = 'Work'
|
||||
|
||||
# Enter Work
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
state = hass.states.get('{}.{}'.format('device_tracker', data['device']))
|
||||
self.assertEqual(state.state, 'work')
|
||||
|
||||
data['id'] = 'Home'
|
||||
data['trigger'] = 'exit'
|
||||
|
||||
# Exit Home
|
||||
req = requests.get(_url(data))
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
state = hass.states.get('{}.{}'.format('device_tracker', data['device']))
|
||||
self.assertEqual(state.state, 'work')
|
73
tests/components/sensor/test_yr.py
Normal file
73
tests/components/sensor/test_yr.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""
|
||||
tests.components.sensor.test_yr
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests Yr sensor.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.components.sensor as sensor
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('betamax_session')
|
||||
class TestSensorYr:
|
||||
""" Test the Yr sensor. """
|
||||
|
||||
def setup_method(self, method):
|
||||
self.hass = ha.HomeAssistant()
|
||||
self.hass.config.latitude = 32.87336
|
||||
self.hass.config.longitude = 117.22743
|
||||
|
||||
def teardown_method(self, method):
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
def test_default_setup(self, betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
assert sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
'elevation': 0,
|
||||
}
|
||||
})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_symbol')
|
||||
|
||||
assert state.state.isnumeric()
|
||||
assert state.attributes.get('unit_of_measurement') is None
|
||||
|
||||
def test_custom_setup(self, betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
assert sensor.setup(self.hass, {
|
||||
'sensor': {
|
||||
'platform': 'yr',
|
||||
'elevation': 0,
|
||||
'monitored_conditions': {
|
||||
'pressure',
|
||||
'windDirection',
|
||||
'humidity',
|
||||
'fog',
|
||||
'windSpeed'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_pressure')
|
||||
assert 'hPa', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_wind_direction')
|
||||
assert '°', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_humidity')
|
||||
assert '%', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_fog')
|
||||
assert '%', state.attributes.get('unit_of_measurement')
|
||||
|
||||
state = self.hass.states.get('sensor.yr_wind_speed')
|
||||
assert 'm/s', state.attributes.get('unit_of_measurement')
|
158
tests/components/switch/test_command_switch.py
Normal file
158
tests/components/switch/test_command_switch.py
Normal file
@ -0,0 +1,158 @@
|
||||
"""
|
||||
tests.components.switch.test_command_switch
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests command switch.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from homeassistant import core
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
import homeassistant.components.switch as switch
|
||||
|
||||
|
||||
class TestCommandSwitch(unittest.TestCase):
|
||||
""" Test the command switch. """
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
self.hass = core.HomeAssistant()
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
def test_state_none(self):
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'switch_status')
|
||||
test_switch = {
|
||||
'oncmd': 'echo 1 > {}'.format(path),
|
||||
'offcmd': 'echo 0 > {}'.format(path),
|
||||
}
|
||||
self.assertTrue(switch.setup(self.hass, {
|
||||
'switch': {
|
||||
'platform': 'command_switch',
|
||||
'switches': {
|
||||
'test': test_switch
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
switch.turn_on(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_ON, state.state)
|
||||
|
||||
switch.turn_off(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
|
||||
def test_state_value(self):
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'switch_status')
|
||||
test_switch = {
|
||||
'statecmd': 'cat {}'.format(path),
|
||||
'oncmd': 'echo 1 > {}'.format(path),
|
||||
'offcmd': 'echo 0 > {}'.format(path),
|
||||
'value_template': '{{ value=="1" }}'
|
||||
}
|
||||
self.assertTrue(switch.setup(self.hass, {
|
||||
'switch': {
|
||||
'platform': 'command_switch',
|
||||
'switches': {
|
||||
'test': test_switch
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
switch.turn_on(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_ON, state.state)
|
||||
|
||||
switch.turn_off(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
|
||||
def test_state_json_value(self):
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'switch_status')
|
||||
oncmd = json.dumps({'status': 'ok'})
|
||||
offcmd = json.dumps({'status': 'nope'})
|
||||
test_switch = {
|
||||
'statecmd': 'cat {}'.format(path),
|
||||
'oncmd': 'echo \'{}\' > {}'.format(oncmd, path),
|
||||
'offcmd': 'echo \'{}\' > {}'.format(offcmd, path),
|
||||
'value_template': '{{ value_json.status=="ok" }}'
|
||||
}
|
||||
self.assertTrue(switch.setup(self.hass, {
|
||||
'switch': {
|
||||
'platform': 'command_switch',
|
||||
'switches': {
|
||||
'test': test_switch
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
switch.turn_on(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_ON, state.state)
|
||||
|
||||
switch.turn_off(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
def test_state_code(self):
|
||||
with tempfile.TemporaryDirectory() as tempdirname:
|
||||
path = os.path.join(tempdirname, 'switch_status')
|
||||
test_switch = {
|
||||
'statecmd': 'cat {}'.format(path),
|
||||
'oncmd': 'echo 1 > {}'.format(path),
|
||||
'offcmd': 'echo 0 > {}'.format(path),
|
||||
}
|
||||
self.assertTrue(switch.setup(self.hass, {
|
||||
'switch': {
|
||||
'platform': 'command_switch',
|
||||
'switches': {
|
||||
'test': test_switch
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
|
||||
switch.turn_on(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_ON, state.state)
|
||||
|
||||
switch.turn_off(self.hass, 'switch.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
state = self.hass.states.get('switch.test')
|
||||
self.assertEqual(STATE_ON, state.state)
|
@ -149,6 +149,45 @@ class TestAlexa(unittest.TestCase):
|
||||
text = req.json().get('response', {}).get('outputSpeech', {}).get('text')
|
||||
self.assertEqual('You told us your sign is virgo.', text)
|
||||
|
||||
def test_intent_request_with_slots_but_no_value(self):
|
||||
data = {
|
||||
'version': '1.0',
|
||||
'session': {
|
||||
'new': False,
|
||||
'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000',
|
||||
'application': {
|
||||
'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe'
|
||||
},
|
||||
'attributes': {
|
||||
'supportedHoroscopePeriods': {
|
||||
'daily': True,
|
||||
'weekly': False,
|
||||
'monthly': False
|
||||
}
|
||||
},
|
||||
'user': {
|
||||
'userId': 'amzn1.account.AM3B00000000000000000000000'
|
||||
}
|
||||
},
|
||||
'request': {
|
||||
'type': 'IntentRequest',
|
||||
'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000',
|
||||
'timestamp': '2015-05-13T12:34:56Z',
|
||||
'intent': {
|
||||
'name': 'GetZodiacHoroscopeIntent',
|
||||
'slots': {
|
||||
'ZodiacSign': {
|
||||
'name': 'ZodiacSign',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
req = _req(data)
|
||||
self.assertEqual(200, req.status_code)
|
||||
text = req.json().get('response', {}).get('outputSpeech', {}).get('text')
|
||||
self.assertEqual('You told us your sign is .', text)
|
||||
|
||||
def test_intent_request_without_slots(self):
|
||||
data = {
|
||||
'version': '1.0',
|
||||
|
@ -5,11 +5,10 @@ tests.test_component_history
|
||||
Tests the history component.
|
||||
"""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import time
|
||||
from datetime import timedelta
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from datetime import timedelta
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.dt as dt_util
|
||||
@ -25,21 +24,24 @@ class TestComponentHistory(unittest.TestCase):
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
""" Init needed objects. """
|
||||
self.hass = get_test_home_assistant(1)
|
||||
self.init_rec = False
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
if self.init_rec:
|
||||
recorder._INSTANCE.block_till_done()
|
||||
os.remove(self.hass.config.path(recorder.DB_FILE))
|
||||
db_path = self.hass.config.path(recorder.DB_FILE)
|
||||
if os.path.isfile(db_path):
|
||||
os.remove(db_path)
|
||||
|
||||
def init_recorder(self):
|
||||
recorder.setup(self.hass, {})
|
||||
self.hass.start()
|
||||
self.wait_recording_done()
|
||||
|
||||
def wait_recording_done(self):
|
||||
""" Block till recording is done. """
|
||||
self.hass.pool.block_till_done()
|
||||
recorder._INSTANCE.block_till_done()
|
||||
self.init_rec = True
|
||||
|
||||
def test_setup(self):
|
||||
""" Test setup method of history. """
|
||||
@ -56,12 +58,11 @@ class TestComponentHistory(unittest.TestCase):
|
||||
for i in range(7):
|
||||
self.hass.states.set(entity_id, "State {}".format(i))
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
if i > 1:
|
||||
states.append(self.hass.states.get(entity_id))
|
||||
|
||||
self.hass.pool.block_till_done()
|
||||
recorder._INSTANCE.block_till_done()
|
||||
|
||||
self.assertEqual(
|
||||
list(reversed(states)), history.last_5_states(entity_id))
|
||||
|
||||
@ -70,22 +71,9 @@ class TestComponentHistory(unittest.TestCase):
|
||||
self.init_recorder()
|
||||
states = []
|
||||
|
||||
for i in range(5):
|
||||
state = ha.State(
|
||||
'test.point_in_time_{}'.format(i % 5),
|
||||
"State {}".format(i),
|
||||
{'attribute_test': i})
|
||||
|
||||
mock_state_change_event(self.hass, state)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
states.append(state)
|
||||
|
||||
recorder._INSTANCE.block_till_done()
|
||||
|
||||
point = dt_util.utcnow() + timedelta(seconds=1)
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=point):
|
||||
now = dt_util.utcnow()
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=now):
|
||||
for i in range(5):
|
||||
state = ha.State(
|
||||
'test.point_in_time_{}'.format(i % 5),
|
||||
@ -93,16 +81,32 @@ class TestComponentHistory(unittest.TestCase):
|
||||
{'attribute_test': i})
|
||||
|
||||
mock_state_change_event(self.hass, state)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
states.append(state)
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
future = now + timedelta(seconds=1)
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=future):
|
||||
for i in range(5):
|
||||
state = ha.State(
|
||||
'test.point_in_time_{}'.format(i % 5),
|
||||
"State {}".format(i),
|
||||
{'attribute_test': i})
|
||||
|
||||
mock_state_change_event(self.hass, state)
|
||||
|
||||
self.wait_recording_done()
|
||||
|
||||
# Get states returns everything before POINT
|
||||
self.assertEqual(states,
|
||||
sorted(history.get_states(point),
|
||||
sorted(history.get_states(future),
|
||||
key=lambda state: state.entity_id))
|
||||
|
||||
# Test get_state here because we have a DB setup
|
||||
self.assertEqual(
|
||||
states[0], history.get_state(point, states[0].entity_id))
|
||||
states[0], history.get_state(future, states[0].entity_id))
|
||||
|
||||
def test_state_changes_during_period(self):
|
||||
self.init_recorder()
|
||||
@ -110,19 +114,20 @@ class TestComponentHistory(unittest.TestCase):
|
||||
|
||||
def set_state(state):
|
||||
self.hass.states.set(entity_id, state)
|
||||
self.hass.pool.block_till_done()
|
||||
recorder._INSTANCE.block_till_done()
|
||||
|
||||
self.wait_recording_done()
|
||||
return self.hass.states.get(entity_id)
|
||||
|
||||
set_state('idle')
|
||||
set_state('YouTube')
|
||||
|
||||
start = dt_util.utcnow()
|
||||
point = start + timedelta(seconds=1)
|
||||
end = point + timedelta(seconds=1)
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=point):
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=start):
|
||||
set_state('idle')
|
||||
set_state('YouTube')
|
||||
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=point):
|
||||
states = [
|
||||
set_state('idle'),
|
||||
set_state('Netflix'),
|
||||
@ -130,10 +135,11 @@ class TestComponentHistory(unittest.TestCase):
|
||||
set_state('YouTube'),
|
||||
]
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow', return_value=end):
|
||||
with patch('homeassistant.components.recorder.dt_util.utcnow',
|
||||
return_value=end):
|
||||
set_state('Netflix')
|
||||
set_state('Plex')
|
||||
|
||||
self.assertEqual(
|
||||
{entity_id: states},
|
||||
history.state_changes_during_period(start, end, entity_id))
|
||||
hist = history.state_changes_during_period(start, end, entity_id)
|
||||
|
||||
self.assertEqual(states, hist[entity_id])
|
||||
|
@ -32,6 +32,60 @@ class TestScene(unittest.TestCase):
|
||||
'scene': [[]]
|
||||
}))
|
||||
|
||||
def test_config_yaml_alias_anchor(self):
|
||||
"""
|
||||
Tests the usage of YAML aliases and anchors. The following test scene
|
||||
configuration is equivalent to:
|
||||
|
||||
scene:
|
||||
- name: test
|
||||
entities:
|
||||
light_1: &light_1_state
|
||||
state: 'on'
|
||||
brightness: 100
|
||||
light_2: *light_1_state
|
||||
|
||||
When encountering a YAML alias/anchor, the PyYAML parser will use a
|
||||
reference to the original dictionary, instead of creating a copy, so
|
||||
care needs to be taken to not modify the original.
|
||||
"""
|
||||
test_light = loader.get_component('light.test')
|
||||
test_light.init()
|
||||
|
||||
self.assertTrue(light.setup(self.hass, {
|
||||
light.DOMAIN: {'platform': 'test'}
|
||||
}))
|
||||
|
||||
light_1, light_2 = test_light.DEVICES[0:2]
|
||||
|
||||
light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id])
|
||||
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
entity_state = {
|
||||
'state': 'on',
|
||||
'brightness': 100,
|
||||
}
|
||||
self.assertTrue(scene.setup(self.hass, {
|
||||
'scene': [{
|
||||
'name': 'test',
|
||||
'entities': {
|
||||
light_1.entity_id: entity_state,
|
||||
light_2.entity_id: entity_state,
|
||||
}
|
||||
}]
|
||||
}))
|
||||
|
||||
scene.activate(self.hass, 'scene.test')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
self.assertTrue(light_1.is_on)
|
||||
self.assertTrue(light_2.is_on)
|
||||
self.assertEqual(100,
|
||||
light_1.last_call('turn_on')[1].get('brightness'))
|
||||
self.assertEqual(100,
|
||||
light_2.last_call('turn_on')[1].get('brightness'))
|
||||
|
||||
def test_activate_scene(self):
|
||||
test_light = loader.get_component('light.test')
|
||||
test_light.init()
|
||||
|
@ -321,6 +321,18 @@ class TestStateMachine(unittest.TestCase):
|
||||
self.assertFalse(self.states.is_state('light.Bowl', 'off'))
|
||||
self.assertFalse(self.states.is_state('light.Non_existing', 'on'))
|
||||
|
||||
def test_is_state_attr(self):
|
||||
""" Test is_state_attr method. """
|
||||
self.states.set("light.Bowl", "on", {"brightness": 100})
|
||||
self.assertTrue(
|
||||
self.states.is_state_attr('light.Bowl', 'brightness', 100))
|
||||
self.assertFalse(
|
||||
self.states.is_state_attr('light.Bowl', 'friendly_name', 200))
|
||||
self.assertFalse(
|
||||
self.states.is_state_attr('light.Bowl', 'friendly_name', 'Bowl'))
|
||||
self.assertFalse(
|
||||
self.states.is_state_attr('light.Non_existing', 'brightness', 100))
|
||||
|
||||
def test_entity_ids(self):
|
||||
""" Test get_entity_ids method. """
|
||||
ent_ids = self.states.entity_ids()
|
||||
|
@ -117,6 +117,14 @@ class TestUtilTemplate(unittest.TestCase):
|
||||
self.hass,
|
||||
'{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}'))
|
||||
|
||||
def test_is_state_attr(self):
|
||||
self.hass.states.set('test.object', 'available', {'mode': 'on'})
|
||||
self.assertEqual(
|
||||
'yes',
|
||||
template.render(
|
||||
self.hass,
|
||||
'{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %}'))
|
||||
|
||||
def test_states_function(self):
|
||||
self.hass.states.set('test.object', 'available')
|
||||
self.assertEqual(
|
||||
|
Loading…
x
Reference in New Issue
Block a user