Merge branch 'dev' into mysensors-component-switch

This commit is contained in:
MartinHjelmare 2016-01-10 04:27:39 +01:00
commit 0b3a66dd1a
54 changed files with 1355 additions and 398 deletions

View File

@ -48,7 +48,6 @@ omit =
homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/locative.py
homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/netgear.py

View File

@ -8,9 +8,7 @@ python:
- 3.4 - 3.4
- 3.5 - 3.5
install: install:
# Validate requirements_all.txt on Python 3.4 - "true"
- 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:
- script/cibuild - script/cibuild
matrix: matrix:

View File

@ -87,13 +87,21 @@ def setup(hass, config):
lambda item: util.split_entity_id(item)[0]) lambda item: util.split_entity_id(item)[0])
for domain, ent_ids in by_domain: for domain, ent_ids in by_domain:
# We want to block for all calls and only return when all calls
# have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are
# listening to the bus too. So as a in between solution, we'll
# block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service)
# Create a new dict for this call # Create a new dict for this call
data = dict(service.data) data = dict(service.data)
# ent_ids is a generator, convert it to a list. # ent_ids is a generator, convert it to a list.
data[ATTR_ENTITY_ID] = list(ent_ids) data[ATTR_ENTITY_ID] = list(ent_ids)
hass.services.call(domain, service.service, data, True) hass.services.call(domain, service.service, data, blocking)
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service) hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service) hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)

View File

@ -67,7 +67,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_DISARMED self._state = STATE_ALARM_DISARMED
elif verisure.ALARM_STATUS[self._id].status == 'armedhome': elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
self._state = STATE_ALARM_ARMED_HOME self._state = STATE_ALARM_ARMED_HOME
elif verisure.ALARM_STATUS[self._id].status == 'armedaway': elif verisure.ALARM_STATUS[self._id].status == 'armed':
self._state = STATE_ALARM_ARMED_AWAY self._state = STATE_ALARM_ARMED_AWAY
elif verisure.ALARM_STATUS[self._id].status != 'pending': elif verisure.ALARM_STATUS[self._id].status != 'pending':
_LOGGER.error( _LOGGER.error(

View File

@ -11,6 +11,7 @@ import logging
from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY
from homeassistant.util import template from homeassistant.util import template
from homeassistant.helpers.service import call_from_config
DOMAIN = 'alexa' DOMAIN = 'alexa'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
@ -23,6 +24,7 @@ API_ENDPOINT = '/api/alexa'
CONF_INTENTS = 'intents' CONF_INTENTS = 'intents'
CONF_CARD = 'card' CONF_CARD = 'card'
CONF_SPEECH = 'speech' CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
def setup(hass, config): def setup(hass, config):
@ -80,6 +82,7 @@ def _handle_alexa(handler, path_match, data):
speech = config.get(CONF_SPEECH) speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD) card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
# pylint: disable=unsubscriptable-object # pylint: disable=unsubscriptable-object
if speech is not None: if speech is not None:
@ -89,6 +92,9 @@ def _handle_alexa(handler, path_match, data):
response.add_card(CardType[card['type']], card['title'], response.add_card(CardType[card['type']], card['title'],
card['content']) card['content'])
if action is not None:
call_from_config(handler.server.hass, action, True)
handler.write_json(response.as_dict()) handler.write_json(response.as_dict())

View File

@ -9,9 +9,9 @@ https://home-assistant.io/components/automation/
import logging import logging
from homeassistant.bootstrap import prepare_setup_platform from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.util import split_entity_id from homeassistant.const import CONF_PLATFORM
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.components import logbook from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config
DOMAIN = 'automation' DOMAIN = 'automation'
@ -19,8 +19,6 @@ DEPENDENCIES = ['group']
CONF_ALIAS = 'alias' CONF_ALIAS = 'alias'
CONF_SERVICE = 'service' CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
CONF_CONDITION = 'condition' CONF_CONDITION = 'condition'
CONF_ACTION = 'action' CONF_ACTION = 'action'
@ -96,22 +94,7 @@ def _get_action(hass, config, name):
_LOGGER.info('Executing %s', name) _LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN) logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
domain, service = split_entity_id(config[CONF_SERVICE]) call_from_config(hass, config)
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if CONF_SERVICE_ENTITY_ID in config:
try:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
except AttributeError:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID]
hass.services.call(domain, service, service_data)
return action return action

View File

@ -82,21 +82,21 @@ def if_action(hass, config):
if before is None: if before is None:
before_func = lambda: None before_func = lambda: None
elif before == EVENT_SUNRISE: elif before == EVENT_SUNRISE:
before_func = lambda: sun.next_rising_utc(hass) + before_offset before_func = lambda: sun.next_rising(hass) + before_offset
else: else:
before_func = lambda: sun.next_setting_utc(hass) + before_offset before_func = lambda: sun.next_setting(hass) + before_offset
if after is None: if after is None:
after_func = lambda: None after_func = lambda: None
elif after == EVENT_SUNRISE: elif after == EVENT_SUNRISE:
after_func = lambda: sun.next_rising_utc(hass) + after_offset after_func = lambda: sun.next_rising(hass) + after_offset
else: else:
after_func = lambda: sun.next_setting_utc(hass) + after_offset after_func = lambda: sun.next_setting(hass) + after_offset
def time_if(): def time_if():
""" Validate time based if-condition """ """ Validate time based if-condition """
now = dt_util.utcnow() now = dt_util.now()
before = before_func() before = before_func()
after = after_func() after = after_func()

View File

@ -6,12 +6,11 @@ The rest binary sensor will consume responses sent by an exposed REST API.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/ https://home-assistant.io/components/binary_sensor.rest/
""" """
from datetime import timedelta
import logging import logging
import requests
from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.util import template, Throttle from homeassistant.util import template
from homeassistant.components.sensor.rest import RestData
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -19,60 +18,33 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'REST Binary Sensor' DEFAULT_NAME = 'REST Binary Sensor'
DEFAULT_METHOD = 'GET' DEFAULT_METHOD = 'GET'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable # pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the REST binary sensor. """ """Setup REST binary sensors."""
use_get = False
use_post = False
resource = config.get('resource', None) resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD) method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None) payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True) verify_ssl = config.get('verify_ssl', True)
if method == 'GET': rest = RestData(method, resource, payload, verify_ssl)
use_get = True rest.update()
elif method == 'POST':
use_post = True
try: if rest.data is None:
if use_get: _LOGGER.error('Unable to fetch Rest data')
response = requests.get(resource, timeout=10, verify=verify_ssl)
elif use_post:
response = requests.post(resource, data=payload, timeout=10,
verify=verify_ssl)
if not response.ok:
_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:// or https:// to your URL")
return False
except requests.exceptions.ConnectionError:
_LOGGER.error('No route to resource/endpoint: %s', resource)
return False return False
if use_get: add_devices([RestBinarySensor(
rest = RestDataGet(resource, verify_ssl) hass, rest, config.get('name', DEFAULT_NAME),
elif use_post: config.get(CONF_VALUE_TEMPLATE))])
rest = RestDataPost(resource, payload, verify_ssl)
add_devices([RestBinarySensor(hass,
rest,
config.get('name', DEFAULT_NAME),
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice): class RestBinarySensor(BinarySensorDevice):
""" Implements a REST binary sensor. """ """REST binary sensor."""
def __init__(self, hass, rest, name, value_template): def __init__(self, hass, rest, name, value_template):
"""Initialize a REST binary sensor."""
self._hass = hass self._hass = hass
self.rest = rest self.rest = rest
self._name = name self._name = name
@ -82,63 +54,20 @@ class RestBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
""" The name of the binary sensor. """ """Name of the binary sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
""" True if the binary sensor is on. """ """Return if the binary sensor is on."""
if self.rest.data is False: if self.rest.data is None:
return False return False
else:
if self._value_template is not None: if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value( self.rest.data = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False) self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data)) return bool(int(self.rest.data))
def update(self): def update(self):
""" Gets the latest data from REST API and updates the state. """ """Get the latest data from REST API and updates the state."""
self.rest.update() self.rest.update()
# pylint: disable=too-few-public-methods
class RestDataGet(object):
""" Class for handling the data retrieval with GET method. """
def __init__(self, resource, verify_ssl):
self._resource = resource
self._verify_ssl = verify_ssl
self.data = False
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service with GET method. """
try:
response = requests.get(self._resource, timeout=10,
verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
self.data = False
# pylint: disable=too-few-public-methods
class RestDataPost(object):
""" Class for handling the data retrieval with POST method. """
def __init__(self, resource, payload, verify_ssl):
self._resource = resource
self._payload = payload
self._verify_ssl = verify_ssl
self.data = False
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service with POST method. """
try:
response = requests.post(self._resource, data=self._payload,
timeout=10, verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
self.data = False

View File

@ -6,65 +6,100 @@ Locative platform for the device tracker.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/ https://home-assistant.io/components/device_tracker.locative/
""" """
import logging
from functools import partial
from homeassistant.const import ( from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR) HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME)
from homeassistant.components.device_tracker import DOMAIN
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
_SEE = 0
URL_API_LOCATIVE_ENDPOINT = "/api/locative" URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up an endpoint for the Locative app. """ """ Set up an endpoint for the Locative 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 # POST would be semantically better, but that currently does not work
# since Locative sends the data as key1=value1&key2=value2 # since Locative sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there. # in the request body, while Home Assistant expects json there.
hass.http.register_path( hass.http.register_path(
'GET', URL_API_LOCATIVE_ENDPOINT, _handle_get_api_locative) 'GET', URL_API_LOCATIVE_ENDPOINT,
partial(_handle_get_api_locative, hass, see))
return True return True
def _handle_get_api_locative(handler, path_match, data): def _handle_get_api_locative(hass, see, handler, path_match, data):
""" Locative message received. """ """ Locative message received. """
if not isinstance(data, dict): if not _check_data(handler, data):
handler.write_json_message(
"Error while parsing Locative message.",
HTTP_INTERNAL_SERVER_ERROR)
return 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: if 'latitude' not in data or 'longitude' not in data:
handler.write_json_message( handler.write_text("Latitude and longitude not specified.",
"Location not specified.", HTTP_UNPROCESSABLE_ENTITY)
HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Latitude and longitude not specified.")
return return False
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: if 'device' not in data:
gps_coords = (float(data['latitude']), float(data['longitude'])) handler.write_text("Device id not specified.",
except ValueError: HTTP_UNPROCESSABLE_ENTITY)
# If invalid latitude / longitude format _LOGGER.error("Device id not specified.")
handler.write_json_message( return False
"Invalid latitude / longitude format.",
HTTP_UNPROCESSABLE_ENTITY)
return
# entity id's in Home Assistant must be alphanumerical if 'id' not in data:
device_uuid = data['device'] handler.write_text("Location id not specified.",
device_entity_id = device_uuid.replace('-', '') HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Location id not specified.")
return False
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id']) if 'trigger' not in data:
handler.write_text("Trigger is not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Trigger is not specified.")
return False
handler.write_json_message("Locative message processed") return True

View File

@ -19,7 +19,7 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear==0.3'] REQUIREMENTS = ['pynetgear==0.3.1']
def get_scanner(hass, config): def get_scanner(hass, config):

View File

@ -10,14 +10,17 @@ import json
import logging import logging
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
from homeassistant.const import (STATE_HOME, STATE_NOT_HOME)
DEPENDENCIES = ['mqtt'] DEPENDENCIES = ['mqtt']
CONF_TRANSITION_EVENTS = 'use_events'
LOCATION_TOPIC = 'owntracks/+/+' LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up a OwnTracksks tracker. """ """ Set up an OwnTracks tracker. """
def owntracks_location_update(topic, payload, qos): def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """ """ MQTT message received. """
@ -48,6 +51,56 @@ def setup_scanner(hass, config, see):
see(**kwargs) see(**kwargs)
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) def owntracks_event_update(topic, payload, qos):
""" MQTT event (geofences) received. """
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
logging.getLogger(__name__).error(
'Unable to parse payload as JSON: %s', payload)
return
if not isinstance(data, dict) or data.get('_type') != 'transition':
return
# check if in "home" fence or other zone
location = ''
if data['event'] == 'enter':
if data['desc'] == 'home':
location = STATE_HOME
else:
location = data['desc']
elif data['event'] == 'leave':
location = STATE_NOT_HOME
else:
logging.getLogger(__name__).error(
'Misformatted mqtt msgs, _type=transition, event=%s',
data['event'])
return
parts = topic.split('/')
kwargs = {
'dev_id': '{}_{}'.format(parts[1], parts[2]),
'host_name': parts[1],
'gps': (data['lat'], data['lon']),
'location_name': location,
}
if 'acc' in data:
kwargs['gps_accuracy'] = data['acc']
see(**kwargs)
use_events = config.get(CONF_TRANSITION_EVENTS)
if use_events:
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
else:
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
return True return True

View File

@ -0,0 +1,33 @@
# Describes the format for available device tracker services
see:
description: Control tracked device
fields:
mac:
description: MAC address of device
example: 'FF:FF:FF:FF:FF:FF'
dev_id:
description: Id of device (find id in known_devices.yaml)
example: 'phonedave'
host_name:
description: Hostname of device
example: 'Dave'
location_name:
description: Name of location where device is located (not_home is away)
example: 'home'
gps:
description: GPS coordinates where device is located (latitude, longitude)
example: '[51.509802, -0.086692]'
gps_accuracy:
description: Accuracy of GPS coordinates
example: '80'
battery:
description: Battery level of device
example: '100'

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "be08c5a3ce12040bbdba2db83cb1a568" VERSION = "72a8220d0db0f7f3702228cd556b8c40"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 50aadaf880a9cb36bf144540171ff5fa029e9eaf Subproject commit 78c348cb7b0a60ba015e3b652e538155d3e94a11

View File

@ -21,7 +21,7 @@ from urllib.parse import urlparse, parse_qs
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.const import ( 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_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH,
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED, 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, json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8")) 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): def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """ """ Returns a file to the user. """
try: try:

View File

@ -7,7 +7,6 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/influxdb/ https://home-assistant.io/components/influxdb/
""" """
import logging import logging
import homeassistant.util as util import homeassistant.util as util
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF, from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
@ -77,6 +76,10 @@ def setup(hass, config):
_state = 0 _state = 0
else: else:
_state = state.state _state = state.state
try:
_state = float(_state)
except ValueError:
pass
measurement = state.attributes.get('unit_of_measurement', state.domain) measurement = state.attributes.get('unit_of_measurement', state.domain)

View File

@ -17,7 +17,7 @@ from urllib.parse import urlparse
from homeassistant.loader import get_component from homeassistant.loader import get_component
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME from homeassistant.const import CONF_HOST, CONF_FILENAME, DEVICE_DEFAULT_NAME
from homeassistant.components.light import ( from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP, Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
@ -35,9 +35,9 @@ _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _find_host_from_config(hass): def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
""" Attempt to detect host based on existing configuration. """ """ Attempt to detect host based on existing configuration. """
path = hass.config.path(PHUE_CONFIG_FILE) path = hass.config.path(filename)
if not os.path.isfile(path): if not os.path.isfile(path):
return None return None
@ -54,13 +54,14 @@ def _find_host_from_config(hass):
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the Hue lights. """ """ Gets the Hue lights. """
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
if discovery_info is not None: if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname host = urlparse(discovery_info[1]).hostname
else: else:
host = config.get(CONF_HOST, None) host = config.get(CONF_HOST, None)
if host is None: if host is None:
host = _find_host_from_config(hass) host = _find_host_from_config(hass, filename)
if host is None: if host is None:
_LOGGER.error('No host found in configuration') _LOGGER.error('No host found in configuration')
@ -70,17 +71,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
if host in _CONFIGURING: if host in _CONFIGURING:
return return
setup_bridge(host, hass, add_devices_callback) setup_bridge(host, hass, add_devices_callback, filename)
def setup_bridge(host, hass, add_devices_callback): def setup_bridge(host, hass, add_devices_callback, filename):
""" Setup a phue bridge based on host parameter. """ """ Setup a phue bridge based on host parameter. """
import phue import phue
try: try:
bridge = phue.Bridge( bridge = phue.Bridge(
host, host,
config_file_path=hass.config.path(PHUE_CONFIG_FILE)) config_file_path=hass.config.path(filename))
except ConnectionRefusedError: # Wrong host was given except ConnectionRefusedError: # Wrong host was given
_LOGGER.exception("Error connecting to the Hue bridge at %s", host) _LOGGER.exception("Error connecting to the Hue bridge at %s", host)
@ -89,7 +90,7 @@ def setup_bridge(host, hass, add_devices_callback):
except phue.PhueRegistrationException: except phue.PhueRegistrationException:
_LOGGER.warning("Connected to Hue at %s but not registered.", host) _LOGGER.warning("Connected to Hue at %s but not registered.", host)
request_configuration(host, hass, add_devices_callback) request_configuration(host, hass, add_devices_callback, filename)
return return
@ -121,10 +122,17 @@ def setup_bridge(host, hass, add_devices_callback):
new_lights = [] 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(): for light_id, info in api_states.items():
if light_id not in lights: if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info, lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights) bridge, update_lights,
bridge_type=bridge_type)
new_lights.append(lights[light_id]) new_lights.append(lights[light_id])
else: else:
lights[light_id].info = info lights[light_id].info = info
@ -135,7 +143,7 @@ def setup_bridge(host, hass, add_devices_callback):
update_lights() update_lights()
def request_configuration(host, hass, add_devices_callback): def request_configuration(host, hass, add_devices_callback, filename):
""" Request configuration steps from the user. """ """ Request configuration steps from the user. """
configurator = get_component('configurator') configurator = get_component('configurator')
@ -149,7 +157,7 @@ def request_configuration(host, hass, add_devices_callback):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def hue_configuration_callback(data): def hue_configuration_callback(data):
""" Actions to do when our configuration callback is called. """ """ Actions to do when our configuration callback is called. """
setup_bridge(host, hass, add_devices_callback) setup_bridge(host, hass, add_devices_callback, filename)
_CONFIGURING[host] = configurator.request_config( _CONFIGURING[host] = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback, hass, "Philips Hue", hue_configuration_callback,
@ -163,11 +171,14 @@ def request_configuration(host, hass, add_devices_callback):
class HueLight(Light): class HueLight(Light):
""" Represents a Hue 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.light_id = light_id
self.info = info self.info = info
self.bridge = bridge self.bridge = bridge
self.update_lights = update_lights self.update_lights = update_lights
self.bridge_type = bridge_type
@property @property
def unique_id(self): def unique_id(self):
@ -227,7 +238,7 @@ class HueLight(Light):
command['alert'] = 'lselect' command['alert'] = 'lselect'
elif flash == FLASH_SHORT: elif flash == FLASH_SHORT:
command['alert'] = 'select' command['alert'] = 'select'
else: elif self.bridge_type == 'hue':
command['alert'] = 'none' command['alert'] = 'none'
effect = kwargs.get(ATTR_EFFECT) effect = kwargs.get(ATTR_EFFECT)
@ -237,7 +248,7 @@ class HueLight(Light):
elif effect == EFFECT_RANDOM: elif effect == EFFECT_RANDOM:
command['hue'] = random.randrange(0, 65535) command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254) command['sat'] = random.randrange(150, 254)
else: elif self.bridge_type == 'hue':
command['effect'] = 'none' command['effect'] = 'none'
self.bridge.set_light(self.light_id, command) self.bridge.set_light(self.light_id, command)

View File

@ -14,9 +14,9 @@ from homeassistant.components.switch.vera import VeraSwitch
from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.light import ATTR_BRIGHTNESS
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/' from homeassistant.const import EVENT_HOMEASSISTANT_STOP
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1'] REQUIREMENTS = ['pyvera==0.2.2']
_LOGGER = logging.getLogger(__name__) _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', {}) 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 = [] devices = []
try: try:
devices = controller.get_devices([ devices = vera_controller.get_devices([
'Switch', 'Switch',
'On/Off Switch', 'On/Off Switch',
'Dimmable Switch']) 'Dimmable Switch'])
@ -54,7 +63,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
exclude = extra_data.get('exclude', False) exclude = extra_data.get('exclude', False)
if exclude is not True: if exclude is not True:
lights.append(VeraLight(device, extra_data)) lights.append(VeraLight(device, vera_controller, extra_data))
add_devices_callback(lights) add_devices_callback(lights)

View File

@ -20,7 +20,7 @@ from homeassistant.components.media_player import (
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
REQUIREMENTS = ['pychromecast==0.6.13'] REQUIREMENTS = ['pychromecast==0.6.14']
CONF_IGNORE_CEC = 'ignore_cec' CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \

View File

@ -142,10 +142,14 @@ class MpdDevice(MediaPlayerDevice):
def media_title(self): def media_title(self):
""" Title of current playing media. """ """ Title of current playing media. """
name = self.currentsong.get('name', None) 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 return title
elif title is None:
return name
else: else:
return '{}: {}'.format(name, title) return '{}: {}'.format(name, title)

View File

@ -35,7 +35,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
def config_from_file(filename, config=None): def config_from_file(filename, config=None):
''' Small configuration file management function''' """ Small configuration file management function. """
if config: if config:
# We're writing configuration # We're writing configuration
try: try:
@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def setup_plexserver(host, token, hass, add_devices_callback): def setup_plexserver(host, token, hass, add_devices_callback):
''' Setup a plexserver based on host parameter''' """ Setup a plexserver based on host parameter. """
import plexapi.server import plexapi.server
import plexapi.exceptions import plexapi.exceptions

View File

@ -149,9 +149,9 @@ class MQTT(object):
} }
if client_id is None: if client_id is None:
self._mqttc = mqtt.Client() self._mqttc = mqtt.Client(protocol=mqtt.MQTTv311)
else: else:
self._mqttc = mqtt.Client(client_id) self._mqttc = mqtt.Client(client_id, protocol=mqtt.MQTTv311)
self._mqttc.user_data_set(self.userdata) self._mqttc.user_data_set(self.userdata)

View File

@ -70,5 +70,8 @@ class EliqSensor(Entity):
def update(self): def update(self):
""" Gets the latest data. """ """ Gets the latest data. """
response = self.api.get_data_now(channelid=self.channel_id) try:
self._state = int(response.power) response = self.api.get_data_now(channelid=self.channel_id)
self._state = int(response.power)
except TypeError: # raised by eliqonline library on any HTTP error
pass

View File

@ -13,14 +13,14 @@ from homeassistant.util import Throttle
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyowm==2.2.1'] REQUIREMENTS = ['pyowm==2.3.0']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = { SENSOR_TYPES = {
'weather': ['Condition', ''], 'weather': ['Condition', ''],
'temperature': ['Temperature', ''], 'temperature': ['Temperature', ''],
'wind_speed': ['Wind speed', 'm/s'], 'wind_speed': ['Wind speed', 'm/s'],
'humidity': ['Humidity', '%'], 'humidity': ['Humidity', '%'],
'pressure': ['Pressure', 'hPa'], 'pressure': ['Pressure', 'mbar'],
'clouds': ['Cloud coverage', '%'], 'clouds': ['Cloud coverage', '%'],
'rain': ['Rain', 'mm'], 'rain': ['Rain', 'mm'],
'snow': ['Snow', 'mm'] 'snow': ['Snow', 'mm']

View File

@ -26,47 +26,21 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable # pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the REST sensor. """ """ Get the REST sensor. """
use_get = False
use_post = False
resource = config.get('resource', None) resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD) method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None) payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True) verify_ssl = config.get('verify_ssl', True)
if method == 'GET': rest = RestData(method, resource, payload, verify_ssl)
use_get = True rest.update()
elif method == 'POST':
use_post = True
try: if rest.data is None:
if use_get: _LOGGER.error('Unable to fetch Rest data')
response = requests.get(resource, timeout=10, verify=verify_ssl)
elif use_post:
response = requests.post(resource, data=payload, timeout=10,
verify=verify_ssl)
if not response.ok:
_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:// or https:// to your URL")
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", resource)
return False return False
if use_get: add_devices([RestSensor(
rest = RestDataGet(resource, verify_ssl) hass, rest, config.get('name', DEFAULT_NAME),
elif use_post: config.get('unit_of_measurement'), config.get(CONF_VALUE_TEMPLATE))])
rest = RestDataPost(resource, payload, verify_ssl)
add_devices([RestSensor(hass,
rest,
config.get('name', DEFAULT_NAME),
config.get('unit_of_measurement'),
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
@ -112,11 +86,11 @@ class RestSensor(Entity):
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class RestDataGet(object): class RestData(object):
""" Class for handling the data retrieval with GET method. """ """Class for handling the data retrieval."""
def __init__(self, resource, verify_ssl): def __init__(self, method, resource, data, verify_ssl):
self._resource = resource self._request = requests.Request(method, resource, data=data).prepare()
self._verify_ssl = verify_ssl self._verify_ssl = verify_ssl
self.data = None self.data = None
@ -124,31 +98,11 @@ class RestDataGet(object):
def update(self): def update(self):
""" Gets the latest data from REST service with GET method. """ """ Gets the latest data from REST service with GET method. """
try: try:
response = requests.get(self._resource, timeout=10, with requests.Session() as sess:
verify=self._verify_ssl) response = sess.send(self._request, timeout=10,
verify=self._verify_ssl)
self.data = response.text self.data = response.text
except requests.exceptions.ConnectionError: except requests.exceptions.RequestException:
_LOGGER.error("No route to resource/endpoint: %s", self._resource) _LOGGER.error("Error fetching data: %s", self._request)
self.data = None
# pylint: disable=too-few-public-methods
class RestDataPost(object):
""" Class for handling the data retrieval with POST method. """
def __init__(self, resource, payload, verify_ssl):
self._resource = resource
self._payload = payload
self._verify_ssl = verify_ssl
self.data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from REST service with POST method. """
try:
response = requests.post(self._resource, data=self._payload,
timeout=10, verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint: %s", self._resource)
self.data = None self.data = None

View File

@ -22,10 +22,22 @@ DEPENDENCIES = ['tellduslive']
SENSOR_TYPE_TEMP = "temp" SENSOR_TYPE_TEMP = "temp"
SENSOR_TYPE_HUMIDITY = "humidity" SENSOR_TYPE_HUMIDITY = "humidity"
SENSOR_TYPE_RAINRATE = "rrate"
SENSOR_TYPE_RAINTOTAL = "rtot"
SENSOR_TYPE_WINDDIRECTION = "wdir"
SENSOR_TYPE_WINDAVERAGE = "wavg"
SENSOR_TYPE_WINDGUST = "wgust"
SENSOR_TYPE_WATT = "watt"
SENSOR_TYPES = { SENSOR_TYPES = {
SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELCIUS, "mdi:thermometer"], SENSOR_TYPE_TEMP: ['Temperature', TEMP_CELCIUS, "mdi:thermometer"],
SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"], SENSOR_TYPE_HUMIDITY: ['Humidity', '%', "mdi:water"],
SENSOR_TYPE_RAINRATE: ['Rain rate', 'mm', "mdi:water"],
SENSOR_TYPE_RAINTOTAL: ['Rain total', 'mm', "mdi:water"],
SENSOR_TYPE_WINDDIRECTION: ['Wind direction', '', ""],
SENSOR_TYPE_WINDAVERAGE: ['Wind average', 'm/s', ""],
SENSOR_TYPE_WINDGUST: ['Wind gust', 'm/s', ""],
SENSOR_TYPE_WATT: ['Watt', 'W', ""],
} }

View File

@ -13,11 +13,9 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import ( 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,
TEMP_CELCIUS, TEMP_FAHRENHEIT) TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/' REQUIREMENTS = ['pyvera==0.2.2']
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +35,16 @@ def get_devices(hass, config):
device_data = config.get('device_data', {}) 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'] categories = ['Temperature Sensor', 'Light Sensor', 'Sensor']
devices = [] devices = []
try: try:
@ -53,7 +60,8 @@ def get_devices(hass, config):
exclude = extra_data.get('exclude', False) exclude = extra_data.get('exclude', False)
if exclude is not True: if exclude is not True:
vera_sensors.append(VeraSensor(device, extra_data)) vera_sensors.append(
VeraSensor(device, vera_controller, extra_data))
return vera_sensors return vera_sensors
@ -66,8 +74,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VeraSensor(Entity): class VeraSensor(Entity):
""" Represents a Vera Sensor. """ """ Represents a Vera Sensor. """
def __init__(self, vera_device, extra_data=None): def __init__(self, vera_device, controller, extra_data=None):
self.vera_device = vera_device self.vera_device = vera_device
self.controller = controller
self.extra_data = extra_data self.extra_data = extra_data
if self.extra_data and self.extra_data.get('name'): if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name') self._name = self.extra_data.get('name')
@ -76,6 +85,16 @@ class VeraSensor(Entity):
self.current_value = '' self.current_value = ''
self._temperature_units = None 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): def __str__(self):
return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state) 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 attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr return attr
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
def update(self): def update(self):
if self.vera_device.category == "Temperature Sensor": if self.vera_device.category == "Temperature Sensor":
self.vera_device.refresh_value('CurrentTemperature') self.vera_device.refresh_value('CurrentTemperature')

View File

@ -1,39 +1,10 @@
""" """
homeassistant.components.sensor.yr homeassistant.components.sensor.yr
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yr.no weather service. Yr.no weather service.
Configuration: For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.yr/
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 logging
@ -54,7 +25,7 @@ SENSOR_TYPES = {
'precipitation': ['Condition', 'mm'], 'precipitation': ['Condition', 'mm'],
'temperature': ['Temperature', '°C'], 'temperature': ['Temperature', '°C'],
'windSpeed': ['Wind speed', 'm/s'], 'windSpeed': ['Wind speed', 'm/s'],
'pressure': ['Pressure', 'hPa'], 'pressure': ['Pressure', 'mbar'],
'windDirection': ['Wind direction', '°'], 'windDirection': ['Wind direction', '°'],
'humidity': ['Humidity', '%'], 'humidity': ['Humidity', '%'],
'fog': ['Fog', '%'], 'fog': ['Fog', '%'],
@ -67,7 +38,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the yr.no sensor. """ """ Get the Yr.no sensor. """
if None in (hass.config.latitude, hass.config.longitude): if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.error("Latitude or longitude not set in Home Assistant config")

View File

@ -18,7 +18,6 @@ from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['astral==0.8.1'] REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun" DOMAIN = "sun"
ENTITY_ID = "sun.sun" ENTITY_ID = "sun.sun"
ENTITY_ID_ELEVATION = "sun.elevation"
CONF_ELEVATION = 'elevation' CONF_ELEVATION = 'elevation'
@ -33,21 +32,21 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Returns if the sun is currently up based on the statemachine. """ """Test if the sun is currently up based on the statemachine."""
entity_id = entity_id or ENTITY_ID entity_id = entity_id or ENTITY_ID
return hass.states.is_state(entity_id, STATE_ABOVE_HORIZON) return hass.states.is_state(entity_id, STATE_ABOVE_HORIZON)
def next_setting(hass, entity_id=None): def next_setting(hass, entity_id=None):
""" Returns the local datetime object of the next sun setting. """ """Local datetime object of the next sun setting."""
utc_next = next_setting_utc(hass, entity_id) utc_next = next_setting_utc(hass, entity_id)
return dt_util.as_local(utc_next) if utc_next else None return dt_util.as_local(utc_next) if utc_next else None
def next_setting_utc(hass, entity_id=None): def next_setting_utc(hass, entity_id=None):
""" Returns the UTC datetime object of the next sun setting. """ """UTC datetime object of the next sun setting."""
entity_id = entity_id or ENTITY_ID entity_id = entity_id or ENTITY_ID
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -62,14 +61,14 @@ def next_setting_utc(hass, entity_id=None):
def next_rising(hass, entity_id=None): def next_rising(hass, entity_id=None):
""" Returns the local datetime object of the next sun rising. """ """Local datetime object of the next sun rising."""
utc_next = next_rising_utc(hass, entity_id) utc_next = next_rising_utc(hass, entity_id)
return dt_util.as_local(utc_next) if utc_next else None return dt_util.as_local(utc_next) if utc_next else None
def next_rising_utc(hass, entity_id=None): def next_rising_utc(hass, entity_id=None):
""" Returns the UTC datetime object of the next sun rising. """ """UTC datetime object of the next sun rising."""
entity_id = entity_id or ENTITY_ID entity_id = entity_id or ENTITY_ID
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -84,7 +83,7 @@ def next_rising_utc(hass, entity_id=None):
def setup(hass, config): def setup(hass, config):
""" Tracks the state of the sun. """ """Track the state of the sun in HA."""
if None in (hass.config.latitude, hass.config.longitude): if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False return False
@ -125,7 +124,7 @@ def setup(hass, config):
class Sun(Entity): class Sun(Entity):
""" Represents the Sun. """ """Represents the Sun."""
entity_id = ENTITY_ID entity_id = ENTITY_ID
@ -158,12 +157,12 @@ class Sun(Entity):
@property @property
def next_change(self): def next_change(self):
""" Returns the datetime when the next change to the state is. """ """Datetime when the next change to the state is."""
return min(self.next_rising, self.next_setting) return min(self.next_rising, self.next_setting)
@property @property
def solar_elevation(self): def solar_elevation(self):
""" Returns the angle the sun is above the horizon""" """Angle the sun is above the horizon."""
from astral import Astral from astral import Astral
return Astral().solar_elevation( return Astral().solar_elevation(
dt_util.utcnow(), dt_util.utcnow(),

View File

@ -10,6 +10,8 @@ import logging
import subprocess import subprocess
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.util import template
_LOGGER = logging.getLogger(__name__) _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(): for dev_name, properties in switches.items():
devices.append( devices.append(
CommandSwitch( CommandSwitch(
hass,
properties.get('name', dev_name), properties.get('name', dev_name),
properties.get('oncmd', 'true'), 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) add_devices_callback(devices)
class CommandSwitch(SwitchDevice): class CommandSwitch(SwitchDevice):
""" Represents a switch that can be togggled using shell commands. """ """ 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._name = name
self._state = False self._state = False
self._command_on = command_on self._command_on = command_on
self._command_off = command_off self._command_off = command_off
self._command_state = command_state
self._value_template = value_template
@staticmethod @staticmethod
def _switch(command): def _switch(command):
@ -51,10 +63,27 @@ class CommandSwitch(SwitchDevice):
return success 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 @property
def should_poll(self): def should_poll(self):
""" No polling needed. """ """ Only poll if we have statecmd. """
return False return self._command_state is not None
@property @property
def name(self): def name(self):
@ -66,14 +95,34 @@ class CommandSwitch(SwitchDevice):
""" True if device is on. """ """ True if device is on. """
return self._state 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): def turn_on(self, **kwargs):
""" Turn the device on. """ """ 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._state = True
self.update_ha_state() self.update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
""" Turn the device off. """ """ 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._state = False
self.update_ha_state() self.update_ha_state()

View File

@ -13,11 +13,13 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import ( 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/' REQUIREMENTS = ['pyvera==0.2.2']
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +39,16 @@ def get_devices(hass, config):
device_data = config.get('device_data', {}) 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 = [] devices = []
try: try:
devices = vera_controller.get_devices([ devices = vera_controller.get_devices([
@ -53,7 +64,8 @@ def get_devices(hass, config):
exclude = extra_data.get('exclude', False) exclude = extra_data.get('exclude', False)
if exclude is not True: if exclude is not True:
vera_switches.append(VeraSwitch(device, extra_data)) vera_switches.append(
VeraSwitch(device, vera_controller, extra_data))
return vera_switches return vera_switches
@ -66,9 +78,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VeraSwitch(ToggleEntity): class VeraSwitch(ToggleEntity):
""" Represents a Vera Switch. """ """ 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.vera_device = vera_device
self.extra_data = extra_data self.extra_data = extra_data
self.controller = controller
if self.extra_data and self.extra_data.get('name'): if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name') self._name = self.extra_data.get('name')
else: else:
@ -77,6 +90,16 @@ class VeraSwitch(ToggleEntity):
# for debouncing status check after command is sent # for debouncing status check after command is sent
self.last_command_send = 0 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 @property
def name(self): def name(self):
""" Get the mame of the switch. """ """ Get the mame of the switch. """
@ -118,6 +141,11 @@ class VeraSwitch(ToggleEntity):
self.vera_device.switch_off() self.vera_device.switch_off()
self.is_on_status = False self.is_on_status = False
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
@property @property
def is_on(self): def is_on(self):
""" True if device is on. """ """ True if device is on. """

View File

@ -12,7 +12,7 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP) STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pywemo==0.3.4'] REQUIREMENTS = ['pywemo==0.3.8']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_WEMO_SUBSCRIPTION_REGISTRY = None _WEMO_SUBSCRIPTION_REGISTRY = None
@ -69,15 +69,14 @@ class WemoSwitch(SwitchDevice):
def _update_callback(self, _device, _params): def _update_callback(self, _device, _params):
""" Called by the wemo device callback to update state. """ """ Called by the wemo device callback to update state. """
_LOGGER.info( _LOGGER.info(
'Subscription update for %s, sevice=%s', 'Subscription update for %s',
self.name, _device) _device)
self.update_ha_state(True) self.update_ha_state(True)
@property @property
def should_poll(self): def should_poll(self):
""" No polling should be needed with subscriptions """ """ No polling needed with subscriptions """
# but leave in for initial version in case of issues. return False
return True
@property @property
def unique_id(self): def unique_id(self):

View File

@ -6,8 +6,9 @@ Adds support for Honeywell Round Connected and Honeywell Evohome thermostats.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/thermostat.honeywell/ https://home-assistant.io/components/thermostat.honeywell/
""" """
import socket
import logging import logging
import socket
from homeassistant.components.thermostat import ThermostatDevice from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
@ -15,6 +16,8 @@ REQUIREMENTS = ['evohomeclient==0.2.4']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature"
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -23,17 +26,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME) username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
try:
away_temp = float(config.get(CONF_AWAY_TEMP, 16))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False
if username is None or password is None: if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s", _LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD) CONF_USERNAME, CONF_PASSWORD)
return False return False
evo_api = EvohomeClient(username, password) evo_api = EvohomeClient(username, password)
try: try:
zones = evo_api.temperatures(force_refresh=True) zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones): for i, zone in enumerate(zones):
add_devices([RoundThermostat(evo_api, zone['id'], i == 0)]) add_devices([RoundThermostat(evo_api,
zone['id'],
i == 0,
away_temp)])
except socket.error: except socket.error:
_LOGGER.error( _LOGGER.error(
"Connection error logging into the honeywell evohome web service" "Connection error logging into the honeywell evohome web service"
@ -44,7 +56,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class RoundThermostat(ThermostatDevice): class RoundThermostat(ThermostatDevice):
""" Represents a Honeywell Round Connected thermostat. """ """ Represents a Honeywell Round Connected thermostat. """
def __init__(self, device, zone_id, master): # pylint: disable=too-many-instance-attributes
def __init__(self, device, zone_id, master, away_temp):
self.device = device self.device = device
self._current_temperature = None self._current_temperature = None
self._target_temperature = None self._target_temperature = None
@ -52,6 +65,8 @@ class RoundThermostat(ThermostatDevice):
self._id = zone_id self._id = zone_id
self._master = master self._master = master
self._is_dhw = False self._is_dhw = False
self._away_temp = away_temp
self._away = False
self.update() self.update()
@property @property
@ -80,6 +95,25 @@ class RoundThermostat(ThermostatDevice):
""" Set new target temperature """ """ Set new target temperature """
self.device.set_temperature(self._name, temperature) self.device.set_temperature(self._name, temperature)
@property
def is_away_mode_on(self):
""" Returns if away mode is on. """
return self._away
def turn_away_mode_on(self):
""" Turns away on.
Evohome does have a proprietary away mode, but it doesn't really work
the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
self.device.set_temperature(self._name, self._away_temp)
def turn_away_mode_off(self):
""" Turns away off. """
self._away = False
self.device.cancel_temp_override(self._name)
def update(self): def update(self):
try: try:
# Only refresh if this is the "master" device, # Only refresh if this is the "master" device,

View File

@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
DISCOVER_ALARMS = 'verisure.alarm_control_panel' DISCOVER_ALARMS = 'verisure.alarm_control_panel'
DEPENDENCIES = ['alarm_control_panel'] DEPENDENCIES = ['alarm_control_panel']
REQUIREMENTS = ['vsure==0.4.3'] REQUIREMENTS = ['vsure==0.4.5']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -24,6 +24,7 @@ CONF_USERNAME = "username"
CONF_PASSWORD = "password" CONF_PASSWORD = "password"
CONF_API_KEY = "api_key" CONF_API_KEY = "api_key"
CONF_ACCESS_TOKEN = "access_token" CONF_ACCESS_TOKEN = "access_token"
CONF_FILENAME = "filename"
CONF_VALUE_TEMPLATE = "value_template" CONF_VALUE_TEMPLATE = "value_template"

View File

@ -468,6 +468,13 @@ class StateMachine(object):
return (entity_id in self._states and return (entity_id in self._states and
self._states[entity_id].state == state) self._states[entity_id].state == state)
def 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()
return (entity_id in self._states and
self._states[entity_id].attributes.get(name, None) == value)
def remove(self, entity_id): def remove(self, entity_id):
"""Remove the state of an entity. """Remove the state of an entity.

View File

@ -36,7 +36,7 @@ def extract_entity_ids(hass, service):
service_ent_id = service.data[ATTR_ENTITY_ID] service_ent_id = service.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, str): if isinstance(service_ent_id, str):
return group.expand_entity_ids(hass, [service_ent_id.lower()]) return group.expand_entity_ids(hass, [service_ent_id])
return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)] return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)]

View File

@ -0,0 +1,43 @@
"""Service calling related helpers."""
import logging
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
_LOGGER = logging.getLogger(__name__)
def call_from_config(hass, config, blocking=False):
"""Call a service based on a config hash."""
if not isinstance(config, dict) or CONF_SERVICE not in config:
_LOGGER.error('Missing key %s: %s', CONF_SERVICE, config)
return
try:
domain, service = split_entity_id(config[CONF_SERVICE])
except ValueError:
_LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE])
return
service_data = config.get(CONF_SERVICE_DATA)
if service_data is None:
service_data = {}
elif isinstance(service_data, dict):
service_data = dict(service_data)
else:
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
entity_id = config.get(CONF_SERVICE_ENTITY_ID)
if isinstance(entity_id, str):
service_data[ATTR_ENTITY_ID] = [ent.strip() for ent in
entity_id.split(",")]
elif entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(domain, service, service_data, blocking)

View File

@ -1,9 +1,6 @@
""" """Helpers that help with state related things."""
homeassistant.helpers.state from collections import defaultdict
~~~~~~~~~~~~~~~~~~~~~~~~~~~ import json
Helpers that help with state related things.
"""
import logging import logging
from homeassistant.core import State from homeassistant.core import State
@ -25,32 +22,36 @@ class TrackStates(object):
that have changed since the start time to the return list when with-block that have changed since the start time to the return list when with-block
is exited. is exited.
""" """
def __init__(self, hass): def __init__(self, hass):
"""Initialize a TrackStates block."""
self.hass = hass self.hass = hass
self.states = [] self.states = []
def __enter__(self): def __enter__(self):
"""Record time from which to track changes."""
self.now = dt_util.utcnow() self.now = dt_util.utcnow()
return self.states return self.states
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
"""Add changes states to changes list."""
self.states.extend(get_changed_since(self.hass.states.all(), self.now)) self.states.extend(get_changed_since(self.hass.states.all(), self.now))
def get_changed_since(states, utc_point_in_time): def get_changed_since(states, utc_point_in_time):
""" """List of states that have been changed since utc_point_in_time."""
Returns all states that have been changed since utc_point_in_time.
"""
point_in_time = dt_util.strip_microseconds(utc_point_in_time) point_in_time = dt_util.strip_microseconds(utc_point_in_time)
return [state for state in states if state.last_updated >= point_in_time] return [state for state in states if state.last_updated >= point_in_time]
def reproduce_state(hass, states, blocking=False): def reproduce_state(hass, states, blocking=False):
""" Takes in a state and will try to have the entity reproduce it. """ """Reproduce given state."""
if isinstance(states, State): if isinstance(states, State):
states = [states] states = [states]
to_call = defaultdict(list)
for state in states: for state in states:
current_state = hass.states.get(state.entity_id) current_state = hass.states.get(state.entity_id)
@ -76,7 +77,18 @@ def reproduce_state(hass, states, blocking=False):
state) state)
continue continue
service_data = dict(state.attributes) if state.domain == 'group':
service_data[ATTR_ENTITY_ID] = state.entity_id service_domain = 'homeassistant'
else:
service_domain = state.domain
hass.services.call(state.domain, service, service_data, blocking) # We group service calls for entities by service call
# json used to create a hashable version of dict with maybe lists in it
key = (service_domain, service,
json.dumps(state.attributes, sort_keys=True))
to_call[key].append(state.entity_id)
for (service_domain, service, service_data), entity_ids in to_call.items():
data = json.loads(service_data)
data[ATTR_ENTITY_ID] = entity_ids
hass.services.call(service_domain, service, data, blocking)

View File

@ -43,7 +43,8 @@ def render(hass, template, variables=None, **kwargs):
try: try:
return ENV.from_string(template, { return ENV.from_string(template, {
'states': AllStates(hass), '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() }).render(kwargs).strip()
except jinja2.TemplateError as err: except jinja2.TemplateError as err:
raise TemplateError(err) raise TemplateError(err)

View File

@ -16,7 +16,7 @@ fuzzywuzzy==0.8.0
pyicloud==0.7.2 pyicloud==0.7.2
# homeassistant.components.device_tracker.netgear # homeassistant.components.device_tracker.netgear
pynetgear==0.3 pynetgear==0.3.1
# homeassistant.components.device_tracker.nmap_tracker # homeassistant.components.device_tracker.nmap_tracker
python-nmap==0.4.3 python-nmap==0.4.3
@ -59,7 +59,7 @@ tellcore-py==1.1.2
# homeassistant.components.light.vera # homeassistant.components.light.vera
# homeassistant.components.sensor.vera # homeassistant.components.sensor.vera
# homeassistant.components.switch.vera # homeassistant.components.switch.vera
https://github.com/pavoni/home-assistant-vera-api/archive/efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip#python-vera==0.1.1 pyvera==0.2.2
# homeassistant.components.wink # homeassistant.components.wink
# homeassistant.components.light.wink # homeassistant.components.light.wink
@ -69,7 +69,7 @@ https://github.com/pavoni/home-assistant-vera-api/archive/efdba4e63d58a30bc9b36d
python-wink==0.3.1 python-wink==0.3.1
# homeassistant.components.media_player.cast # homeassistant.components.media_player.cast
pychromecast==0.6.13 pychromecast==0.6.14
# homeassistant.components.media_player.kodi # homeassistant.components.media_player.kodi
jsonrpc-requests==0.1 jsonrpc-requests==0.1
@ -135,7 +135,7 @@ eliqonline==1.0.11
python-forecastio==1.3.3 python-forecastio==1.3.3
# homeassistant.components.sensor.openweathermap # homeassistant.components.sensor.openweathermap
pyowm==2.2.1 pyowm==2.3.0
# homeassistant.components.sensor.rpi_gpio # homeassistant.components.sensor.rpi_gpio
# homeassistant.components.switch.rpi_gpio # homeassistant.components.switch.rpi_gpio
@ -173,7 +173,7 @@ hikvision==0.4
orvibo==1.1.0 orvibo==1.1.0
# homeassistant.components.switch.wemo # homeassistant.components.switch.wemo
pywemo==0.3.4 pywemo==0.3.8
# homeassistant.components.tellduslive # homeassistant.components.tellduslive
tellive-py==0.5.2 tellive-py==0.5.2
@ -191,7 +191,7 @@ python-nest==2.6.0
radiotherm==1.2 radiotherm==1.2
# homeassistant.components.verisure # homeassistant.components.verisure
vsure==0.4.3 vsure==0.4.5
# homeassistant.components.zwave # homeassistant.components.zwave
pydispatcher==2.0.5 pydispatcher==2.0.5

View File

@ -5,6 +5,28 @@
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
if [ "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then
echo "Verifying requirements_all.txt..."
python3 setup.py -q develop 2> /dev/null
tput setaf 1
script/gen_requirements_all.py validate
VERIFY_REQUIREMENTS_STATUS=$?
tput sgr0
else
VERIFY_REQUIREMENTS_STATUS=0
fi
if [ "$VERIFY_REQUIREMENTS_STATUS" != "0" ]; then
exit $VERIFY_REQUIREMENTS_STATUS
fi
script/bootstrap_server > /dev/null
DEP_INSTALL_STATUS=$?
if [ "$DEP_INSTALL_STATUS" != "0" ]; then
exit $DEP_INSTALL_STATUS
fi
if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then
NO_LINT=1 NO_LINT=1
fi fi

View File

@ -5,13 +5,6 @@
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
if [ "$NO_LINT" = "1" ]; then
LINT_STATUS=0
else
script/lint
LINT_STATUS=$?
fi
echo "Running tests..." echo "Running tests..."
if [ "$1" = "coverage" ]; then if [ "$1" = "coverage" ]; then
@ -22,6 +15,13 @@ else
TEST_STATUS=$? TEST_STATUS=$?
fi fi
if [ "$NO_LINT" = "1" ]; then
LINT_STATUS=0
else
script/lint
LINT_STATUS=$?
fi
if [ $LINT_STATUS -eq 0 ] if [ $LINT_STATUS -eq 0 ]
then then
exit $TEST_STATUS exit $TEST_STATUS

View File

@ -162,14 +162,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
@ -197,14 +197,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
@ -233,14 +233,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
@ -269,14 +269,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
@ -306,21 +306,60 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC)
with patch('homeassistant.components.automation.sun.dt_util.utcnow', with patch('homeassistant.components.automation.sun.dt_util.now',
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_different_tz(self):
import pytz
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
sun.STATE_ATTR_NEXT_SETTING: '17:30:00 16-09-2015',
})
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'condition': {
'platform': 'sun',
'after': 'sunset',
},
'action': {
'service': 'test.automation'
}
}
})
# Before
now = datetime(2015, 9, 16, 17, tzinfo=pytz.timezone('US/Mountain'))
with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
# After
now = datetime(2015, 9, 16, 18, tzinfo=pytz.timezone('US/Mountain'))
with patch('homeassistant.components.automation.sun.dt_util.now',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()

View 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')

View File

@ -4,12 +4,14 @@ tests.components.sensor.test_yr
Tests Yr sensor. Tests Yr sensor.
""" """
from datetime import datetime
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.components.sensor as sensor import homeassistant.components.sensor as sensor
import homeassistant.util.dt as dt_util
@pytest.mark.usefixtures('betamax_session') @pytest.mark.usefixtures('betamax_session')
@ -26,14 +28,18 @@ class TestSensorYr:
self.hass.stop() self.hass.stop()
def test_default_setup(self, betamax_session): def test_default_setup(self, betamax_session):
now = datetime(2016, 1, 5, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session', with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session): return_value=betamax_session):
assert sensor.setup(self.hass, { with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
'sensor': { return_value=now):
'platform': 'yr', assert sensor.setup(self.hass, {
'elevation': 0, 'sensor': {
} 'platform': 'yr',
}) 'elevation': 0,
}
})
state = self.hass.states.get('sensor.yr_symbol') state = self.hass.states.get('sensor.yr_symbol')

View 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)

View File

@ -27,12 +27,13 @@ API_URL = "http://127.0.0.1:{}{}".format(SERVER_PORT, alexa.API_ENDPOINT)
HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD} HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD}
hass = None hass = None
calls = []
@patch('homeassistant.components.http.util.get_local_ip', @patch('homeassistant.components.http.util.get_local_ip',
return_value='127.0.0.1') return_value='127.0.0.1')
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """ """Initalize a Home Assistant server for testing this module."""
global hass global hass
hass = ha.HomeAssistant() hass = ha.HomeAssistant()
@ -42,6 +43,8 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
http.CONF_SERVER_PORT: SERVER_PORT}}) http.CONF_SERVER_PORT: SERVER_PORT}})
hass.services.register('test', 'alexa', lambda call: calls.append(call))
bootstrap.setup_component(hass, alexa.DOMAIN, { bootstrap.setup_component(hass, alexa.DOMAIN, {
'alexa': { 'alexa': {
'intents': { 'intents': {
@ -61,7 +64,20 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
'GetZodiacHoroscopeIntent': { 'GetZodiacHoroscopeIntent': {
'speech': { 'speech': {
'type': 'plaintext', 'type': 'plaintext',
'text': 'You told us your sign is {{ ZodiacSign }}.' 'text': 'You told us your sign is {{ ZodiacSign }}.',
}
},
'CallServiceIntent': {
'speech': {
'type': 'plaintext',
'text': 'Service called',
},
'action': {
'service': 'test.alexa',
'data': {
'hello': 1
},
'entity_id': 'switch.test',
} }
} }
} }
@ -231,6 +247,39 @@ class TestAlexa(unittest.TestCase):
text = req.json().get('response', {}).get('outputSpeech', {}).get('text') text = req.json().get('response', {}).get('outputSpeech', {}).get('text')
self.assertEqual('You are both home, you silly', text) self.assertEqual('You are both home, you silly', text)
def test_intent_request_calling_service(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': {},
'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': 'CallServiceIntent',
}
}
}
call_count = len(calls)
req = _req(data)
self.assertEqual(200, req.status_code)
self.assertEqual(call_count + 1, len(calls))
call = calls[-1]
self.assertEqual('test', call.domain)
self.assertEqual('alexa', call.service)
self.assertEqual(['switch.test'], call.data.get('entity_id'))
self.assertEqual(1, call.data.get('hello'))
def test_session_ended_request(self): def test_session_ended_request(self):
data = { data = {
'version': '1.0', 'version': '1.0',

View File

@ -6,20 +6,22 @@ Tests core compoments.
""" """
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
import unittest import unittest
from unittest.mock import patch
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.loader as loader
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF) STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components as comps import homeassistant.components as comps
from tests.common import get_test_home_assistant
class TestComponentsCore(unittest.TestCase): class TestComponentsCore(unittest.TestCase):
""" Tests homeassistant.components module. """ """ Tests homeassistant.components module. """
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """ """ Init needed objects. """
self.hass = ha.HomeAssistant() self.hass = get_test_home_assistant()
self.assertTrue(comps.setup(self.hass, {})) self.assertTrue(comps.setup(self.hass, {}))
self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Bowl', STATE_ON)
@ -58,3 +60,24 @@ class TestComponentsCore(unittest.TestCase):
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(runs)) self.assertEqual(1, len(runs))
@patch('homeassistant.core.ServiceRegistry.call')
def test_turn_on_to_not_block_for_domains_without_service(self, mock_call):
self.hass.services.register('light', SERVICE_TURN_ON, lambda x: x)
# We can't test if our service call results in services being called
# because by mocking out the call service method, we mock out all
# So we mimick how the service registry calls services
service_call = ha.ServiceCall('homeassistant', 'turn_on', {
'entity_id': ['light.test', 'sensor.bla', 'light.bla']
})
self.hass.services._services['homeassistant']['turn_on'](service_call)
self.assertEqual(2, mock_call.call_count)
self.assertEqual(
('light', 'turn_on', {'entity_id': ['light.bla', 'light.test']},
True),
mock_call.call_args_list[0][0])
self.assertEqual(
('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False),
mock_call.call_args_list[1][0])

View File

@ -0,0 +1,68 @@
"""
tests.helpers.test_service
~~~~~~~~~~~~~~~~~~~~~~~~~~
Test service helpers.
"""
import unittest
from unittest.mock import patch
from homeassistant.const import SERVICE_TURN_ON
from homeassistant.helpers import service
from tests.common import get_test_home_assistant, mock_service
class TestServiceHelpers(unittest.TestCase):
"""
Tests the Home Assistant service helpers.
"""
def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test_domain', 'test_service')
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_split_entity_string(self):
service.call_from_config(self.hass, {
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer'
})
self.hass.pool.block_till_done()
self.assertEqual(['hello.world', 'sensor.beer'],
self.calls[-1].data.get('entity_id'))
def test_not_mutate_input(self):
orig = {
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer',
'data': {
'hello': 1,
},
}
service.call_from_config(self.hass, orig)
self.hass.pool.block_till_done()
self.assertEqual({
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer',
'data': {
'hello': 1,
},
}, orig)
@patch('homeassistant.helpers.service._LOGGER.error')
def test_fail_silently_if_no_service(self, mock_log):
service.call_from_config(self.hass, None)
self.assertEqual(1, mock_log.call_count)
service.call_from_config(self.hass, {})
self.assertEqual(2, mock_log.call_count)
service.call_from_config(self.hass, {
'service': 'invalid'
})
self.assertEqual(3, mock_log.call_count)

148
tests/helpers/test_state.py Normal file
View File

@ -0,0 +1,148 @@
"""
tests.helpers.test_state
~~~~~~~~~~~~~~~~~~~~~~~~
Test state helpers.
"""
from datetime import timedelta
import unittest
from unittest.mock import patch
import homeassistant.core as ha
import homeassistant.components as core_components
from homeassistant.const import SERVICE_TURN_ON
from homeassistant.util import dt as dt_util
from homeassistant.helpers import state
from tests.common import get_test_home_assistant, mock_service
class TestStateHelpers(unittest.TestCase):
"""
Tests the Home Assistant event helpers.
"""
def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """
self.hass = get_test_home_assistant()
core_components.setup(self.hass, {})
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_get_changed_since(self):
point1 = dt_util.utcnow()
point2 = point1 + timedelta(seconds=5)
point3 = point2 + timedelta(seconds=5)
with patch('homeassistant.core.dt_util.utcnow', return_value=point1):
self.hass.states.set('light.test', 'on')
state1 = self.hass.states.get('light.test')
with patch('homeassistant.core.dt_util.utcnow', return_value=point2):
self.hass.states.set('light.test2', 'on')
state2 = self.hass.states.get('light.test2')
with patch('homeassistant.core.dt_util.utcnow', return_value=point3):
self.hass.states.set('light.test3', 'on')
state3 = self.hass.states.get('light.test3')
self.assertEqual(
[state2, state3],
state.get_changed_since([state1, state2, state3], point2))
def test_track_states(self):
point1 = dt_util.utcnow()
point2 = point1 + timedelta(seconds=5)
point3 = point2 + timedelta(seconds=5)
with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow:
mock_utcnow.return_value = point2
with state.TrackStates(self.hass) as states:
mock_utcnow.return_value = point1
self.hass.states.set('light.test', 'on')
mock_utcnow.return_value = point2
self.hass.states.set('light.test2', 'on')
state2 = self.hass.states.get('light.test2')
mock_utcnow.return_value = point3
self.hass.states.set('light.test3', 'on')
state3 = self.hass.states.get('light.test3')
self.assertEqual(
sorted([state2, state3], key=lambda state: state.entity_id),
sorted(states, key=lambda state: state.entity_id))
def test_reproduce_state_with_turn_on(self):
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test', 'off')
state.reproduce_state(self.hass, ha.State('light.test', 'on'))
self.hass.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('light', last_call.domain)
self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(['light.test'], last_call.data.get('entity_id'))
def test_reproduce_state_with_complex_service_data(self):
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test', 'off')
complex_data = ['hello', {'11': '22'}]
state.reproduce_state(self.hass, ha.State('light.test', 'on', {
'complex': complex_data
}))
self.hass.pool.block_till_done()
self.assertTrue(len(calls) > 0)
last_call = calls[-1]
self.assertEqual('light', last_call.domain)
self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(complex_data, last_call.data.get('complex'))
def test_reproduce_state_with_group(self):
light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('group.test', 'off', {
'entity_id': ['light.test1', 'light.test2']})
state.reproduce_state(self.hass, ha.State('group.test', 'on'))
self.hass.pool.block_till_done()
self.assertEqual(1, len(light_calls))
last_call = light_calls[-1]
self.assertEqual('light', last_call.domain)
self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(['light.test1', 'light.test2'],
last_call.data.get('entity_id'))
def test_reproduce_state_group_states_with_same_domain_and_data(self):
light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
self.hass.states.set('light.test1', 'off')
self.hass.states.set('light.test2', 'off')
state.reproduce_state(self.hass, [
ha.State('light.test1', 'on', {'brightness': 95}),
ha.State('light.test2', 'on', {'brightness': 95})])
self.hass.pool.block_till_done()
self.assertEqual(1, len(light_calls))
last_call = light_calls[-1]
self.assertEqual('light', last_call.domain)
self.assertEqual(SERVICE_TURN_ON, last_call.service)
self.assertEqual(['light.test1', 'light.test2'],
last_call.data.get('entity_id'))
self.assertEqual(95, last_call.data.get('brightness'))

View File

@ -321,6 +321,18 @@ class TestStateMachine(unittest.TestCase):
self.assertFalse(self.states.is_state('light.Bowl', 'off')) self.assertFalse(self.states.is_state('light.Bowl', 'off'))
self.assertFalse(self.states.is_state('light.Non_existing', 'on')) 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): def test_entity_ids(self):
""" Test get_entity_ids method. """ """ Test get_entity_ids method. """
ent_ids = self.states.entity_ids() ent_ids = self.states.entity_ids()

View File

@ -117,6 +117,14 @@ class TestUtilTemplate(unittest.TestCase):
self.hass, self.hass,
'{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}')) '{% 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): def test_states_function(self):
self.hass.states.set('test.object', 'available') self.hass.states.set('test.object', 'available')
self.assertEqual( self.assertEqual(