Merge pull request #883 from balloob/dev

0.11.0
pull/956/head 0.11
Paulus Schoutsen 2016-01-16 11:23:40 -08:00
commit bd536be66d
130 changed files with 5670 additions and 1332 deletions

View File

@ -5,6 +5,8 @@ omit =
homeassistant/__main__.py
# omit pieces of code that rely on external devices being present
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@ -15,6 +17,10 @@ omit =
homeassistant/components/*/modbus.py
homeassistant/components/*/tellstick.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
@ -32,6 +38,12 @@ omit =
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
@ -41,7 +53,6 @@ omit =
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/geofancy.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/netgear.py
@ -70,6 +81,7 @@ omit =
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
@ -89,10 +101,9 @@ omit =
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
@ -108,13 +119,13 @@ omit =
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_gpio.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/heatmiser.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/honeywell.py
homeassistant/components/thermostat/nest.py
homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py

1
.gitignore vendored
View File

@ -41,6 +41,7 @@ Icon
dist
build
eggs
.eggs
parts
bin
var

View File

@ -8,9 +8,7 @@ python:
- 3.4
- 3.5
install:
# Validate requirements_all.txt on Python 3.5
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then python3 setup.py develop; script/gen_requirements_all.py validate; fi
- script/bootstrap_server
- "true"
script:
- script/cibuild
matrix:

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 Paulus Schoutsen
Copyright (c) 2016 Paulus Schoutsen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -275,7 +275,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
datefmt='%y-%m-%d %H:%M:%S'))
logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.setLevel(logging.NOTSET) # this sets the minimum log level
logger.setLevel(logging.INFO)
else:
_LOGGER.error(

View File

@ -87,13 +87,21 @@ def setup(hass, config):
lambda item: util.split_entity_id(item)[0])
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
data = dict(service.data)
# ent_ids is a generator, convert it to a list.
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_ON, handle_turn_service)

View File

@ -0,0 +1,118 @@
"""
homeassistant.components.alarm_control_panel.alarmdotcom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import (
STATE_UNKNOWN,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom'
'/archive/0.0.7.zip'
'#pyalarmdotcom==0.0.7']
DEFAULT_NAME = 'Alarm.com'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup an Alarm.com control panel. """
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
if username is None or password is None:
_LOGGER.error('Must specify username and password!')
return False
add_devices([AlarmDotCom(hass,
config.get('name', DEFAULT_NAME),
config.get('code'),
username,
password)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class AlarmDotCom(alarm.AlarmControlPanel):
""" Represents a Alarm.com status. """
def __init__(self, hass, name, code, username, password):
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10)
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
@property
def should_poll(self):
return True
@property
def name(self):
return self._name
@property
def code_format(self):
""" One or more characters if code is defined. """
return None if self._code is None else '.+'
@property
def state(self):
""" Returns the state of the device. """
if self._alarm.state == 'Disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay':
return STATE_ALARM_ARMED_HOME
elif self._alarm.state == 'Armed Away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
""" Send disarm command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.disarm()
self.update_ha_state()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_stay()
self.update_ha_state()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_away()
self.update_ha_state()
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check

View File

@ -68,7 +68,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def state(self):
""" Returns the state of the device. """
if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING

View File

@ -29,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
alarms.extend([
VerisureAlarm(value)
for value in verisure.get_alarm_status().values()
for value in verisure.ALARM_STATUS.values()
if verisure.SHOW_ALARM
])
@ -42,7 +42,6 @@ class VerisureAlarm(alarm.AlarmControlPanel):
def __init__(self, alarm_status):
self._id = alarm_status.id
self._device = verisure.MY_PAGES.DEVICE_ALARM
self._state = STATE_UNKNOWN
@property
@ -58,40 +57,40 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
""" Four digit code required. """
return '^\\d{4}$'
return '^\\d{%s}$' % verisure.CODE_DIGITS
def update(self):
""" Update alarm status """
verisure.update()
verisure.update_alarm()
if verisure.STATUS[self._device][self._id].status == 'unarmed':
if verisure.ALARM_STATUS[self._id].status == 'unarmed':
self._state = STATE_ALARM_DISARMED
elif verisure.STATUS[self._device][self._id].status == 'armedhome':
elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
self._state = STATE_ALARM_ARMED_HOME
elif verisure.STATUS[self._device][self._id].status == 'armedaway':
elif verisure.ALARM_STATUS[self._id].status == 'armed':
self._state = STATE_ALARM_ARMED_AWAY
elif verisure.STATUS[self._device][self._id].status != 'pending':
elif verisure.ALARM_STATUS[self._id].status != 'pending':
_LOGGER.error(
'Unknown alarm state %s',
verisure.STATUS[self._device][self._id].status)
verisure.ALARM_STATUS[self._id].status)
def alarm_disarm(self, code=None):
""" Send disarm command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_DISARMED)
_LOGGER.warning('disarming')
verisure.MY_PAGES.alarm.set(code, 'DISARMED')
_LOGGER.info('verisure alarm disarming')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_HOME)
_LOGGER.warning('arming home')
verisure.MY_PAGES.alarm.set(code, 'ARMED_HOME')
_LOGGER.info('verisure alarm arming home')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()
def alarm_arm_away(self, code=None):
""" Send arm away command. """
verisure.MY_PAGES.set_alarm_status(
code,
verisure.MY_PAGES.ALARM_ARMED_AWAY)
_LOGGER.warning('arming away')
verisure.MY_PAGES.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info('verisure alarm arming away')
verisure.MY_PAGES.alarm.wait_while_pending()
verisure.update_alarm()

View File

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

View File

@ -9,9 +9,9 @@ https://home-assistant.io/components/automation/
import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config
DOMAIN = 'automation'
@ -19,8 +19,6 @@ DEPENDENCIES = ['group']
CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
@ -96,22 +94,7 @@ def _get_action(hass, config, name):
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
domain, service = split_entity_id(config[CONF_SERVICE])
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)
call_from_config(hass, config)
return action

View File

@ -6,6 +6,7 @@ Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
"""
from functools import partial
import logging
from homeassistant.const import CONF_VALUE_TEMPLATE
@ -20,6 +21,14 @@ CONF_ABOVE = "above"
_LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state):
"""Render state value."""
if value_template is None:
return state.state
return template.render(hass, value_template, {'state': state})
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
@ -38,12 +47,7 @@ def trigger(hass, config, action):
CONF_BELOW, CONF_ABOVE)
return False
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
@ -79,12 +83,7 @@ def if_action(hass, config):
CONF_BELOW, CONF_ABOVE)
return None
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
def if_numeric_state():
""" Test numeric state condition. """

View File

@ -17,6 +17,10 @@ DEPENDENCIES = ['sun']
CONF_OFFSET = 'offset'
CONF_EVENT = 'event'
CONF_BEFORE = "before"
CONF_BEFORE_OFFSET = "before_offset"
CONF_AFTER = "after"
CONF_AFTER_OFFSET = "after_offset"
EVENT_SUNSET = 'sunset'
EVENT_SUNRISE = 'sunrise'
@ -37,26 +41,9 @@ def trigger(hass, config, action):
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
return False
if CONF_OFFSET in config:
raw_offset = config.get(CONF_OFFSET)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if negative_offset:
offset *= -1
else:
offset = timedelta(0)
offset = _parse_offset(config.get(CONF_OFFSET))
if offset is False:
return False
# Do something to call action
if event == EVENT_SUNRISE:
@ -67,6 +54,77 @@ def trigger(hass, config, action):
return True
def if_action(hass, config):
""" Wraps action method with sun based condition. """
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
# Make sure required configuration keys are present
if before is None and after is None:
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s or %s",
CONF_BEFORE, CONF_AFTER)
return None
# Make sure configuration keys have the right value
if before not in (None, EVENT_SUNRISE, EVENT_SUNSET) or \
after not in (None, EVENT_SUNRISE, EVENT_SUNSET):
logging.getLogger(__name__).error(
"%s and %s can only be set to %s or %s",
CONF_BEFORE, CONF_AFTER, EVENT_SUNRISE, EVENT_SUNSET)
return None
before_offset = _parse_offset(config.get(CONF_BEFORE_OFFSET))
after_offset = _parse_offset(config.get(CONF_AFTER_OFFSET))
if before_offset is False or after_offset is False:
return None
if before is None:
def before_func():
"""Return no point in time."""
return None
elif before == EVENT_SUNRISE:
def before_func():
"""Return time before sunrise."""
return sun.next_rising(hass) + before_offset
else:
def before_func():
"""Return time before sunset."""
return sun.next_setting(hass) + before_offset
if after is None:
def after_func():
"""Return no point in time."""
return None
elif after == EVENT_SUNRISE:
def after_func():
"""Return time after sunrise."""
return sun.next_rising(hass) + after_offset
else:
def after_func():
"""Return time after sunset."""
return sun.next_setting(hass) + after_offset
def time_if():
""" Validate time based if-condition """
now = dt_util.now()
before = before_func()
after = after_func()
if before is not None and now > now.replace(hour=before.hour,
minute=before.minute):
return False
if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
return False
return True
return time_if
def trigger_sunrise(hass, action, offset):
""" Trigger action at next sun rise. """
def next_rise():
@ -103,3 +161,26 @@ def trigger_sunset(hass, action, offset):
action()
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
def _parse_offset(raw_offset):
if raw_offset is None:
return timedelta(0)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if negative_offset:
offset *= -1
return offset

View File

@ -32,8 +32,8 @@ def trigger(hass, config, action):
_error_time(config[CONF_AFTER], CONF_AFTER)
return False
hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config
or CONF_SECONDS in config):
elif (CONF_HOURS in config or CONF_MINUTES in config or
CONF_SECONDS in config):
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)

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
https://home-assistant.io/components/binary_sensor.rest/
"""
from datetime import timedelta
import logging
import requests
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
_LOGGER = logging.getLogger(__name__)
@ -19,61 +18,33 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'REST Binary Sensor'
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
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the REST binary sensor. """
use_get = False
use_post = False
"""Setup REST binary sensors."""
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True)
if method == 'GET':
use_get = True
elif method == 'POST':
use_post = True
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
try:
if use_get:
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:// to your URL.')
return False
except requests.exceptions.ConnectionError:
_LOGGER.error('No route to resource/endpoint: %s',
resource)
if rest.data is None:
_LOGGER.error('Unable to fetch Rest data')
return False
if use_get:
rest = RestDataGet(resource, verify_ssl)
elif use_post:
rest = RestDataPost(resource, payload, verify_ssl)
add_devices([RestBinarySensor(hass,
rest,
config.get('name', DEFAULT_NAME),
config.get(CONF_VALUE_TEMPLATE))])
add_devices([RestBinarySensor(
hass, rest, config.get('name', DEFAULT_NAME),
config.get(CONF_VALUE_TEMPLATE))])
# pylint: disable=too-many-arguments
class RestBinarySensor(BinarySensorDevice):
""" Implements a REST binary sensor. """
"""REST binary sensor."""
def __init__(self, hass, rest, name, value_template):
"""Initialize a REST binary sensor."""
self._hass = hass
self.rest = rest
self._name = name
@ -83,63 +54,20 @@ class RestBinarySensor(BinarySensorDevice):
@property
def name(self):
""" The name of the binary sensor. """
"""Name of the binary sensor."""
return self._name
@property
def is_on(self):
""" True if the binary sensor is on. """
if self.rest.data is False:
"""Return if the binary sensor is on."""
if self.rest.data is None:
return False
else:
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
if self._value_template is not None:
self.rest.data = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
return bool(int(self.rest.data))
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()
# 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

@ -0,0 +1,73 @@
"""
homeassistant.components.binary_sensor.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a binary_sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rpi_gpio/
"""
import logging
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (DEVICE_DEFAULT_NAME)
DEFAULT_PULL_MODE = "UP"
DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Raspberry PI GPIO devices. """
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
binary_sensors = []
ports = config.get('ports')
for port_num, port_name in ports.items():
binary_sensors.append(RPiGPIOBinarySensor(
port_name, port_num, pull_mode, bouncetime, invert_logic))
add_devices(binary_sensors)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOBinarySensor(BinarySensorDevice):
""" Represents a binary sensor that uses Raspberry Pi GPIO. """
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port = port
self._pull_mode = pull_mode
self._bouncetime = bouncetime
self._invert_logic = invert_logic
rpi_gpio.setup_input(self._port, self._pull_mode)
self._state = rpi_gpio.read_input(self._port)
def read_gpio(port):
""" Reads state from GPIO. """
self._state = rpi_gpio.read_input(self._port)
self.update_ha_state()
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def is_on(self):
""" Returns the state of the entity. """
return self._state != self._invert_logic

View File

@ -58,8 +58,8 @@ class AsusWrtDeviceScanner(object):
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.lock = threading.Lock()

View File

@ -1,70 +0,0 @@
"""
homeassistant.components.device_tracker.geofancy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Geofancy platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofancy/
"""
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
DEPENDENCIES = ['http']
_SEE = 0
URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Geofancy app. """
# Use a global variable to keep setup_scanner compact when using a callback
global _SEE
_SEE = see
# POST would be semantically better, but that currently does not work
# since Geofancy sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
return True
def _handle_get_api_geofancy(handler, path_match, data):
""" Geofancy message received. """
if not isinstance(data, dict):
handler.write_json_message(
"Error while parsing Geofancy message.",
HTTP_INTERNAL_SERVER_ERROR)
return
if 'latitude' not in data or 'longitude' not in data:
handler.write_json_message(
"Location not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
if 'device' not in data or 'id' not in data:
handler.write_json_message(
"Device id or location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
try:
gps_coords = (float(data['latitude']), float(data['longitude']))
except ValueError:
# If invalid latitude / longitude format
handler.write_json_message(
"Invalid latitude / longitude format.",
HTTP_UNPROCESSABLE_ENTITY)
return
# entity id's in Home Assistant must be alphanumerical
device_uuid = data['device']
device_entity_id = device_uuid.replace('-', '')
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
handler.write_json_message("Geofancy message processed")

View File

@ -0,0 +1,104 @@
"""
homeassistant.components.device_tracker.locative
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Locative platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/
"""
import logging
from functools import partial
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME)
from homeassistant.components.device_tracker import DOMAIN
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Locative app. """
# POST would be semantically better, but that currently does not work
# since Locative sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_LOCATIVE_ENDPOINT,
partial(_handle_get_api_locative, hass, see))
return True
def _handle_get_api_locative(hass, see, handler, path_match, data):
""" Locative message received. """
if not _check_data(handler, data):
return
device = data['device'].replace('-', '')
location_name = data['id'].lower()
direction = data['trigger']
if direction == 'enter':
see(dev_id=device, location_name=location_name)
handler.write_text("Setting location to {}".format(location_name))
elif direction == 'exit':
current_state = hass.states.get("{}.{}".format(DOMAIN, device))
if current_state is None or current_state.state == location_name:
see(dev_id=device, location_name=STATE_NOT_HOME)
handler.write_text("Setting location to not home")
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered before
# the previous zone was exited. The enter message will be sent
# first, then the exit message will be sent second.
handler.write_text(
'Ignoring exit from {} (already in {})'.format(
location_name, current_state))
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
handler.write_text("Received test message.")
else:
handler.write_text(
"Received unidentified message: {}".format(direction),
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Received unidentified message from Locative: %s",
direction)
def _check_data(handler, data):
if 'latitude' not in data or 'longitude' not in data:
handler.write_text("Latitude and longitude not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Latitude and longitude not specified.")
return False
if 'device' not in data:
handler.write_text("Device id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Device id not specified.")
return False
if 'id' not in data:
handler.write_text("Location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Location id not specified.")
return False
if 'trigger' not in data:
handler.write_text("Trigger is not specified.",
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error("Trigger is not specified.")
return False
return True

View File

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

View File

@ -10,14 +10,17 @@ import json
import logging
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (STATE_HOME, STATE_NOT_HOME)
DEPENDENCIES = ['mqtt']
CONF_TRANSITION_EVENTS = 'use_events'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
def setup_scanner(hass, config, see):
""" Set up a OwnTracksks tracker. """
""" Set up an OwnTracks tracker. """
def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """
@ -48,6 +51,56 @@ def setup_scanner(hass, config, see):
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'].lower() == '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

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

@ -105,8 +105,7 @@ class SnmpScanner(object):
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
errindex and restable[-1][int(errindex)-1] or '?')
return
for resrow in restable:

View File

@ -242,8 +242,8 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
.format(self.host, self.stok)
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,

View File

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

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 50aadaf880a9cb36bf144540171ff5fa029e9eaf
Subproject commit 2ecd6a818443780dc5d0d981996d165218b2b094

View File

@ -21,7 +21,7 @@ from urllib.parse import urlparse, parse_qs
import homeassistant.core as ha
from homeassistant.const import (
SERVER_PORT, CONTENT_TYPE_JSON,
SERVER_PORT, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN,
HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING,
HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH,
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED,
@ -112,10 +112,10 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
_LOGGER.info("running http in development mode")
if ssl_certificate is not None:
wrap_kwargs = {'certfile': ssl_certificate}
if ssl_key is not None:
wrap_kwargs['keyfile'] = ssl_key
self.socket = ssl.wrap_socket(self.socket, **wrap_kwargs)
context = ssl.create_default_context(
purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(ssl_certificate, keyfile=ssl_key)
self.socket = context.wrap_socket(self.socket, server_side=True)
def start(self):
""" Starts the HTTP server. """
@ -198,12 +198,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
self.authenticated = (self.server.api_password is None
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password
or data.get(DATA_API_PASSWORD) ==
self.server.api_password
or self.verify_session())
self.authenticated = (self.server.api_password is None or
self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password or
data.get(DATA_API_PASSWORD) ==
self.server.api_password or
self.verify_session())
if '_METHOD' in data:
method = data.pop('_METHOD')
@ -293,6 +293,17 @@ class RequestHandler(SimpleHTTPRequestHandler):
json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8"))
def write_text(self, message, status_code=HTTP_OK):
""" Helper method to return a text message to the caller. """
self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
self.set_session_cookie_header()
self.end_headers()
self.wfile.write(message.encode("UTF-8"))
def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """
try:

View File

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

View File

@ -0,0 +1,124 @@
"""
Component to keep track of user controlled booleans for within automation.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_boolean/
"""
import logging
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.util import slugify
DOMAIN = 'input_boolean'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_NAME = "name"
CONF_INITIAL = "initial"
CONF_ICON = "icon"
def is_on(hass, entity_id):
"""Test if input_boolean is True."""
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id):
"""Set input_boolean to True."""
hass.services.call(DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
def turn_off(hass, entity_id):
"""Set input_boolean to False."""
hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
def setup(hass, config):
"""Set up input booleans."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = []
for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
cfg = {}
name = cfg.get(CONF_NAME)
state = cfg.get(CONF_INITIAL, False)
icon = cfg.get(CONF_ICON)
entities.append(InputBoolean(object_id, name, state, icon))
if not entities:
return False
def toggle_service(service):
"""Handle a calls to the input boolean services."""
target_inputs = component.extract_from_service(service)
for input_b in target_inputs:
if service.service == SERVICE_TURN_ON:
input_b.turn_on()
else:
input_b.turn_off()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service)
component.add_entities(entities)
return True
class InputBoolean(ToggleEntity):
"""Represent a boolean input within Home Assistant."""
def __init__(self, object_id, name, state, icon):
"""Initialize a boolean input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._state = state
self._icon = icon
@property
def should_poll(self):
"""If entitiy should be polled."""
return False
@property
def name(self):
"""Name of the boolean input."""
return self._name
@property
def icon(self):
"""Icon to be used for this entity."""
return self._icon
@property
def is_on(self):
"""True if entity is on."""
return self._state
def turn_on(self, **kwargs):
"""Turn the entity on."""
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
"""Turn the entity off."""
self._state = False
self.update_ha_state()

View File

@ -50,6 +50,7 @@ FLASH_LONG = "long"
# Apply an effect to the light, can be EFFECT_COLORLOOP
ATTR_EFFECT = "effect"
EFFECT_COLORLOOP = "colorloop"
EFFECT_RANDOM = "random"
EFFECT_WHITE = "white"
LIGHT_PROFILES_FILE = "light_profiles.csv"
@ -228,7 +229,8 @@ def setup(hass, config):
if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
params[ATTR_FLASH] = dat[ATTR_FLASH]
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE):
if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE,
EFFECT_RANDOM):
params[ATTR_EFFECT] = dat[ATTR_EFFECT]
for light in target_lights:

View File

@ -10,17 +10,18 @@ import json
import logging
import os
import socket
import random
from datetime import timedelta
from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as 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 (
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
ATTR_EFFECT, EFFECT_COLORLOOP, ATTR_RGB_COLOR)
ATTR_EFFECT, EFFECT_COLORLOOP, EFFECT_RANDOM, ATTR_RGB_COLOR)
REQUIREMENTS = ['phue==0.8']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -34,9 +35,9 @@ _CONFIGURING = {}
_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. """
path = hass.config.path(PHUE_CONFIG_FILE)
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
@ -53,13 +54,14 @@ def _find_host_from_config(hass):
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the Hue lights. """
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname
else:
host = config.get(CONF_HOST, None)
if host is None:
host = _find_host_from_config(hass)
host = _find_host_from_config(hass, filename)
if host is None:
_LOGGER.error('No host found in configuration')
@ -69,17 +71,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
if host in _CONFIGURING:
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. """
import phue
try:
bridge = phue.Bridge(
host,
config_file_path=hass.config.path(PHUE_CONFIG_FILE))
config_file_path=hass.config.path(filename))
except ConnectionRefusedError: # Wrong host was given
_LOGGER.exception("Error connecting to the Hue bridge at %s", host)
@ -88,7 +90,7 @@ def setup_bridge(host, hass, add_devices_callback):
except phue.PhueRegistrationException:
_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
@ -120,10 +122,17 @@ def setup_bridge(host, hass, add_devices_callback):
new_lights = []
api_name = api.get('config').get('name')
if api_name == 'RaspBee-GW':
bridge_type = 'deconz'
else:
bridge_type = 'hue'
for light_id, info in api_states.items():
if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights)
bridge, update_lights,
bridge_type=bridge_type)
new_lights.append(lights[light_id])
else:
lights[light_id].info = info
@ -134,7 +143,7 @@ def setup_bridge(host, hass, add_devices_callback):
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. """
configurator = get_component('configurator')
@ -148,7 +157,7 @@ def request_configuration(host, hass, add_devices_callback):
# pylint: disable=unused-argument
def hue_configuration_callback(data):
""" 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(
hass, "Philips Hue", hue_configuration_callback,
@ -162,11 +171,14 @@ def request_configuration(host, hass, add_devices_callback):
class HueLight(Light):
""" Represents a Hue light """
def __init__(self, light_id, info, bridge, update_lights):
# pylint: disable=too-many-arguments
def __init__(self, light_id, info, bridge, update_lights,
bridge_type='hue'):
self.light_id = light_id
self.info = info
self.bridge = bridge
self.update_lights = update_lights
self.bridge_type = bridge_type
@property
def unique_id(self):
@ -226,14 +238,17 @@ class HueLight(Light):
command['alert'] = 'lselect'
elif flash == FLASH_SHORT:
command['alert'] = 'select'
else:
elif self.bridge_type == 'hue':
command['alert'] = 'none'
effect = kwargs.get(ATTR_EFFECT)
if effect == EFFECT_COLORLOOP:
command['effect'] = 'colorloop'
else:
elif effect == EFFECT_RANDOM:
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.bridge_type == 'hue':
command['effect'] = 'none'
self.bridge.set_light(self.light_id, command)

View File

@ -13,8 +13,9 @@ from homeassistant.components.light import Light
from homeassistant.util import slugify
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.rfxtrx import ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, \
ATTR_NAME, EVENT_BUTTON_PRESSED
from homeassistant.components.rfxtrx import (
ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID,
ATTR_NAME, EVENT_BUTTON_PRESSED)
DEPENDENCIES = ['rfxtrx']

View File

@ -42,6 +42,7 @@ turn_on:
description: Light effect
values:
- colorloop
- random
turn_off:
description: Turn a light off

View File

@ -7,16 +7,15 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.vera/
"""
import logging
import time
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
from homeassistant.components.light import ATTR_BRIGHTNESS
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_ON
REQUIREMENTS = ['pyvera==0.2.7']
_LOGGER = logging.getLogger(__name__)
@ -36,10 +35,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
device_data = config.get('device_data', {})
controller = veraApi.VeraController(base_url)
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
""" Shutdown Vera subscriptions and subscription thread on exit"""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
devices = []
try:
devices = controller.get_devices([
devices = vera_controller.get_devices([
'Switch',
'On/Off Switch',
'Dimmable Switch'])
@ -50,11 +58,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lights = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
lights.append(VeraLight(device, extra_data))
lights.append(VeraLight(device, vera_controller, extra_data))
add_devices_callback(lights)
@ -77,5 +85,5 @@ class VeraLight(VeraSwitch):
else:
self.vera_device.switch_on()
self.last_command_send = time.time()
self.is_on_status = True
self._state = STATE_ON
self.update_ha_state(True)

View File

@ -12,7 +12,7 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.3.1']
REQUIREMENTS = ['python-wink==0.4.1']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):

View File

@ -11,7 +11,7 @@ import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.3.1']
REQUIREMENTS = ['python-wink==0.4.1']
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -28,7 +28,7 @@ QUERY_EVENTS_BETWEEN = """
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
"""
EVENT_LOGBOOK_ENTRY = 'LOGBOOK_ENTRY'
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
GROUP_BY_MINUTES = 15
@ -204,7 +204,7 @@ def humanify(events):
event.time_fired, "Home Assistant", action,
domain=HA_DOMAIN)
elif event.event_type == EVENT_LOGBOOK_ENTRY:
elif event.event_type.lower() == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN)
entity_id = event.data.get(ATTR_ENTITY_ID)
if domain is None and entity_id is not None:

View File

@ -76,8 +76,12 @@ def setup(hass, config=None):
logfilter[LOGGER_LOGS] = logs
logger = logging.getLogger('')
logger.setLevel(logging.NOTSET)
# Set log filter for all log handler
for handler in logging.root.handlers:
handler.setLevel(logging.NOTSET)
handler.addFilter(HomeAssistantLogFilter(logfilter))
return True

View File

@ -72,6 +72,7 @@ SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
SUPPORT_VOLUME_STEP = 1024
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'

View File

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

View File

@ -142,10 +142,14 @@ class MpdDevice(MediaPlayerDevice):
def media_title(self):
""" Title of current playing media. """
name = self.currentsong.get('name', None)
title = self.currentsong['title']
title = self.currentsong.get('title', None)
if name is None:
if name is None and title is None:
return "None"
elif name is None:
return title
elif title is None:
return name
else:
return '{}: {}'.format(name, title)

View File

@ -35,7 +35,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
def config_from_file(filename, config=None):
''' Small configuration file management function'''
""" Small configuration file management function. """
if config:
# We're writing configuration
try:
@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-branches
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.exceptions

View File

@ -22,9 +22,9 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
@ -202,11 +202,10 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" Image url of current playing media. """
if 'artwork_url' in self._status:
return self._status['artwork_url']
return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\
.format(
server=self._lms.host,
port=self._lms.http_port,
player=self._id)
return ('http://{server}:{port}/music/current/cover.jpg?'
'player={player}').format(server=self._lms.host,
port=self._lms.http_port,
player=self._id)
@property
def media_title(self):

View File

@ -0,0 +1,438 @@
"""
homeassistant.components.media_player.universal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Combines multiple media players into one for a universal controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.universal/
"""
# pylint: disable=import-error
from copy import copy
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.service import call_from_config
from homeassistant.const import (
STATE_IDLE, STATE_ON, STATE_OFF, CONF_NAME,
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE,
SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
from homeassistant.components.media_player import (
MediaPlayerDevice, DOMAIN,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_TRACK, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_ALBUM_ARTIST,
ATTR_MEDIA_SEASON, ATTR_MEDIA_EPISODE, ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_PLAYLIST, ATTR_APP_ID, ATTR_APP_NAME, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_SEEK_POSITION)
ATTR_ACTIVE_CHILD = 'active_child'
CONF_ATTRS = 'attributes'
CONF_CHILDREN = 'children'
CONF_COMMANDS = 'commands'
CONF_PLATFORM = 'platform'
CONF_SERVICE = 'service'
CONF_SERVICE_DATA = 'service_data'
CONF_STATE = 'state'
OFF_STATES = [STATE_IDLE, STATE_OFF]
REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" sets up the universal media players """
if not validate_config(config):
return
player = UniversalMediaPlayer(hass,
config[CONF_NAME],
config[CONF_CHILDREN],
config[CONF_COMMANDS],
config[CONF_ATTRS])
add_devices([player])
def validate_config(config):
""" validate universal media player configuration """
del config[CONF_PLATFORM]
# validate name
if CONF_NAME not in config:
_LOGGER.error('Universal Media Player configuration requires name')
return False
validate_children(config)
validate_commands(config)
validate_attributes(config)
del_keys = []
for key in config:
if key not in [CONF_NAME, CONF_CHILDREN, CONF_COMMANDS, CONF_ATTRS]:
_LOGGER.warning(
'Universal Media Player (%s) unrecognized parameter %s',
config[CONF_NAME], key)
del_keys.append(key)
for key in del_keys:
del config[key]
return True
def validate_children(config):
""" validate children """
if CONF_CHILDREN not in config:
_LOGGER.info(
'No children under Universal Media Player (%s)', config[CONF_NAME])
config[CONF_CHILDREN] = []
elif not isinstance(config[CONF_CHILDREN], list):
_LOGGER.warning(
'Universal Media Player (%s) children not list in config. '
'They will be ignored.',
config[CONF_NAME])
config[CONF_CHILDREN] = []
def validate_commands(config):
""" validate commands """
if CONF_COMMANDS not in config:
config[CONF_COMMANDS] = {}
elif not isinstance(config[CONF_COMMANDS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified commands not dict in '
'config. They will be ignored.',
config[CONF_NAME])
config[CONF_COMMANDS] = {}
def validate_attributes(config):
""" validate attributes """
if CONF_ATTRS not in config:
config[CONF_ATTRS] = {}
elif not isinstance(config[CONF_ATTRS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified attributes '
'not dict in config. They will be ignored.',
config[CONF_NAME])
config[CONF_ATTRS] = {}
for key, val in config[CONF_ATTRS].items():
attr = val.split('|', 1)
if len(attr) == 1:
attr.append(None)
config[CONF_ATTRS][key] = attr
class UniversalMediaPlayer(MediaPlayerDevice):
""" Represents a universal media player in HA """
# pylint: disable=too-many-public-methods
def __init__(self, hass, name, children, commands, attributes):
# pylint: disable=too-many-arguments
self.hass = hass
self._name = name
self._children = children
self._cmds = commands
self._attrs = attributes
self._child_state = None
def on_dependency_update(*_):
""" update ha state when dependencies update """
self.update_ha_state(True)
depend = copy(children)
for entity in attributes.values():
depend.append(entity[0])
track_state_change(hass, depend, on_dependency_update)
def _entity_lkp(self, entity_id, state_attr=None):
""" Looks up an entity state from hass """
state_obj = self.hass.states.get(entity_id)
if state_obj is None:
return
if state_attr:
return state_obj.attributes.get(state_attr)
return state_obj.state
def _override_or_child_attr(self, attr_name):
""" returns either the override or the active child for attr_name """
if attr_name in self._attrs:
return self._entity_lkp(self._attrs[attr_name][0],
self._attrs[attr_name][1])
return self._child_attr(attr_name)
def _child_attr(self, attr_name):
""" returns the active child's attr """
active_child = self._child_state
return active_child.attributes.get(attr_name) if active_child else None
def _call_service(self, service_name, service_data=None,
allow_override=False):
""" calls either a specified or active child's service """
if allow_override and service_name in self._cmds:
call_from_config(
self.hass, self._cmds[service_name], blocking=True)
return
if service_data is None:
service_data = {}
active_child = self._child_state
service_data[ATTR_ENTITY_ID] = active_child.entity_id
self.hass.services.call(DOMAIN, service_name, service_data,
blocking=True)
@property
def should_poll(self):
""" Indicates whether HA should poll for updates """
return False
@property
def master_state(self):
""" gets the master state from entity or none """
if CONF_STATE in self._attrs:
master_state = self._entity_lkp(self._attrs[CONF_STATE][0],
self._attrs[CONF_STATE][1])
return master_state if master_state else STATE_OFF
else:
return None
def _cache_active_child_state(self):
""" The state of the active child or None """
for child_name in self._children:
child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES:
self._child_state = child_state
return
self._child_state = None
@property
def name(self):
""" name of universal player """
return self._name
@property
def state(self):
"""
Current state of media player
Off if master state is off
ELSE Status of first active child
ELSE master state or off
"""
master_state = self.master_state # avoid multiple lookups
if master_state == STATE_OFF:
return STATE_OFF
active_child = self._child_state
if active_child:
return active_child.state
return master_state if master_state else STATE_OFF
@property
def volume_level(self):
""" Volume level of entity specified in attributes or active child """
return self._child_attr(ATTR_MEDIA_VOLUME_LEVEL)
@property
def is_volume_muted(self):
""" boolean if volume is muted """
return self._override_or_child_attr(ATTR_MEDIA_VOLUME_MUTED) \
in [True, STATE_ON]
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_ID)
@property
def media_content_type(self):
""" Content type of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_TYPE)
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
return self._child_attr(ATTR_MEDIA_DURATION)
@property
def media_image_url(self):
""" Image url of current playing media. """
return self._child_attr(ATTR_ENTITY_PICTURE)
@property
def media_title(self):
""" Title of current playing media. """
return self._child_attr(ATTR_MEDIA_TITLE)
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ARTIST)
@property
def media_album_name(self):
""" Album name of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_NAME)
@property
def media_album_artist(self):
""" Album arist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_ARTIST)
@property
def media_track(self):
""" Track number of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_TRACK)
@property
def media_series_title(self):
""" Series title of current playing media. (TV Show only)"""
return self._child_attr(ATTR_MEDIA_SERIES_TITLE)
@property
def media_season(self):
""" Season of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_SEASON)
@property
def media_episode(self):
""" Episode of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_EPISODE)
@property
def media_channel(self):
""" Channel currently playing. """
return self._child_attr(ATTR_MEDIA_CHANNEL)
@property
def media_playlist(self):
""" Title of Playlist currently playing. """
return self._child_attr(ATTR_MEDIA_PLAYLIST)
@property
def app_id(self):
""" ID of the current running app. """
return self._child_attr(ATTR_APP_ID)
@property
def app_name(self):
""" Name of the current running app. """
return self._child_attr(ATTR_APP_NAME)
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0
if SERVICE_TURN_ON in self._cmds:
flags |= SUPPORT_TURN_ON
if SERVICE_TURN_OFF in self._cmds:
flags |= SUPPORT_TURN_OFF
if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN]]):
flags |= SUPPORT_VOLUME_STEP
flags &= ~SUPPORT_VOLUME_SET
if SERVICE_VOLUME_MUTE in self._cmds and \
ATTR_MEDIA_VOLUME_MUTED in self._attrs:
flags |= SUPPORT_VOLUME_MUTE
return flags
@property
def device_state_attributes(self):
""" Extra attributes a device wants to expose. """
active_child = self._child_state
return {ATTR_ACTIVE_CHILD: active_child.entity_id} \
if active_child else {}
def turn_on(self):
""" turn the media player on. """
self._call_service(SERVICE_TURN_ON, allow_override=True)
def turn_off(self):
""" turn the media player off. """
self._call_service(SERVICE_TURN_OFF, allow_override=True)
def mute_volume(self, is_volume_muted):
""" mute the volume. """
data = {ATTR_MEDIA_VOLUME_MUTED: is_volume_muted}
self._call_service(SERVICE_VOLUME_MUTE, data, allow_override=True)
def set_volume_level(self, volume_level):
""" set volume level, range 0..1. """
data = {ATTR_MEDIA_VOLUME_LEVEL: volume_level}
self._call_service(SERVICE_VOLUME_SET, data)
def media_play(self):
""" Send play commmand. """
self._call_service(SERVICE_MEDIA_PLAY)
def media_pause(self):
""" Send pause command. """
self._call_service(SERVICE_MEDIA_PAUSE)
def media_previous_track(self):
""" Send previous track command. """
self._call_service(SERVICE_MEDIA_PREVIOUS_TRACK)
def media_next_track(self):
""" Send next track command. """
self._call_service(SERVICE_MEDIA_NEXT_TRACK)
def media_seek(self, position):
""" Send seek command. """
data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
data = {'media_id': media_id}
self._call_service(SERVICE_YOUTUBE_VIDEO, data)
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id}
self._call_service(SERVICE_PLAY_MEDIA, data)
def volume_up(self):
""" volume_up media player. """
self._call_service(SERVICE_VOLUME_UP, allow_override=True)
def volume_down(self):
""" volume_down media player. """
self._call_service(SERVICE_VOLUME_DOWN, allow_override=True)
def media_play_pause(self):
""" media_play_pause media player. """
self._call_service(SERVICE_MEDIA_PLAY_PAUSE)
def update(self):
""" event to trigger a state update in HA """
for child_name in self._children:
child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES:
self._child_state = child_state
return
self._child_state = None

View File

@ -30,7 +30,7 @@ DEFAULT_QOS = 0
DEFAULT_RETAIN = False
SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
REQUIREMENTS = ['paho-mqtt==1.1']
@ -149,9 +149,9 @@ class MQTT(object):
}
if client_id is None:
self._mqttc = mqtt.Client()
self._mqttc = mqtt.Client(protocol=mqtt.MQTTv311)
else:
self._mqttc = mqtt.Client(client_id)
self._mqttc = mqtt.Client(client_id, protocol=mqtt.MQTTv311)
self._mqttc.user_data_set(self.userdata)

View File

@ -0,0 +1,114 @@
"""
homeassistant.components.mqtt_eventstream
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect two Home Assistant instances via mqtt.
Configuration:
To use the mqtt_eventstream component you will need to add the following to
your configuration.yaml file.
If you do not specify a publish_topic you will not forward events to the queue.
If you do not specify a subscribe_topic then you will not receive events from
the remote server.
mqtt_eventstream:
publish_topic: MyServerName
subscribe_topic: OtherHaServerName
"""
import json
from homeassistant.core import EventOrigin, State
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH
from homeassistant.const import (
MATCH_ALL,
EVENT_TIME_CHANGED,
EVENT_CALL_SERVICE,
EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED,
)
import homeassistant.loader as loader
from homeassistant.remote import JSONEncoder
# The domain of your component. Should be equal to the name of your component
DOMAIN = "mqtt_eventstream"
# List of component names (string) your component depends upon
DEPENDENCIES = ['mqtt']
def setup(hass, config):
""" Setup our mqtt_eventstream component. """
mqtt = loader.get_component('mqtt')
pub_topic = config[DOMAIN].get('publish_topic', None)
sub_topic = config[DOMAIN].get('subscribe_topic', None)
def _event_publisher(event):
""" Handle events by publishing them on the mqtt queue. """
if event.origin != EventOrigin.local:
return
if event.event_type == EVENT_TIME_CHANGED:
return
# Filter out the events that were triggered by publishing
# to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE:
if (
event.data.get('domain') == MQTT_DOMAIN and
event.data.get('service') == MQTT_SVC_PUBLISH and
event.data.get('topic') == pub_topic
):
return
# Filter out all the "event service executed" events because they
# are only used internally by core as callbacks for blocking
# during the interval while a service is being executed.
# They will serve no purpose to the external system,
# and thus are unnecessary traffic.
# And at any rate it would cause an infinite loop to publish them
# because publishing to an MQTT topic itself triggers one.
if event.event_type == EVENT_SERVICE_EXECUTED:
return
event_info = {'event_type': event.event_type, 'event_data': event.data}
msg = json.dumps(event_info, cls=JSONEncoder)
mqtt.publish(hass, pub_topic, msg)
# Only listen for local events if you are going to publish them
if pub_topic:
hass.bus.listen(MATCH_ALL, _event_publisher)
# Process events from a remote server that are received on a queue
def _event_receiver(topic, payload, qos):
"""
Receive events published by the other HA instance and fire
them on this hass instance.
"""
event = json.loads(payload)
event_type = event.get('event_type')
event_data = event.get('event_data')
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
# Copied over from the _handle_api_post_events_event method
# of the api component.
if event_type == EVENT_STATE_CHANGED and event_data:
for key in ('old_state', 'new_state'):
state = State.from_dict(event_data.get(key))
if state:
event_data[key] = state
hass.bus.fire(
event_type,
event_data=event_data,
origin=EventOrigin.remote
)
# Only subscribe if you specified a topic
if sub_topic:
mqtt.subscribe(hass, sub_topic, _event_receiver)
hass.states.set('{domain}.initialized'.format(domain=DOMAIN), True)
# return boolean to indicate that initialization was successful
return True

View File

@ -0,0 +1,230 @@
"""
homeassistant.components.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MySensors component that connects to a MySensors gateway via pymysensors
API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors.html
New features:
New MySensors component.
Updated MySensors Sensor platform.
New MySensors Switch platform. Currently only in optimistic mode (compare
with MQTT).
Multiple gateways are now supported.
Configuration.yaml:
mysensors:
gateways:
- port: '/dev/ttyUSB0'
persistence_file: 'path/mysensors.json'
- port: '/dev/ttyACM1'
persistence_file: 'path/mysensors2.json'
debug: true
persistence: true
version: '1.5'
"""
import logging
from homeassistant.helpers import validate_config
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED,
TEMP_CELCIUS,)
CONF_GATEWAYS = 'gateways'
CONF_PORT = 'port'
CONF_DEBUG = 'debug'
CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version'
DEFAULT_VERSION = '1.4'
DOMAIN = 'mysensors'
DEPENDENCIES = []
REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/'
'005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4']
_LOGGER = logging.getLogger(__name__)
ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id'
ATTR_PORT = 'port'
GATEWAYS = None
SCAN_INTERVAL = 30
DISCOVER_SENSORS = "mysensors.sensors"
DISCOVER_SWITCHES = "mysensors.switches"
# Maps discovered services to their platforms
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS),
('switch', DISCOVER_SWITCHES),
]
def setup(hass, config):
"""Setup the MySensors component."""
# pylint: disable=too-many-locals
if not validate_config(config,
{DOMAIN: [CONF_GATEWAYS]},
_LOGGER):
return False
import mysensors.mysensors as mysensors
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def setup_gateway(port, persistence, persistence_file, version):
"""Return gateway after setup of the gateway."""
gateway = mysensors.SerialGateway(port, event_callback=None,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
gateway.metric = is_metric
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
gateway = GatewayWrapper(gateway, version)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
def gw_start(event):
"""Callback to trigger start of gateway and any persistence."""
gateway.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
if persistence:
for node_id in gateway.sensors:
gateway.event_callback('persistence', node_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start)
return gateway
# Setup all ports from config
global GATEWAYS
GATEWAYS = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
if isinstance(conf_gateways, dict):
conf_gateways = [conf_gateways]
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
for index, gway in enumerate(conf_gateways):
port = gway[CONF_PORT]
persistence_file = gway.get(
CONF_PERSISTENCE_FILE,
hass.config.path('mysensors{}.pickle'.format(index + 1)))
GATEWAYS[port] = setup_gateway(
port, persistence, persistence_file, version)
for (component, discovery_service) in DISCOVERY_COMPONENTS:
# Ensure component is loaded
if not bootstrap.setup_component(hass, component, config):
return False
# Fire discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: discovery_service,
ATTR_DISCOVERED: {}})
return True
def pf_callback_factory(
s_types, v_types, devices, add_devices, entity_class):
"""Return a new callback for the platform."""
def mysensors_callback(gateway, node_id):
"""Callback for mysensors platform."""
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
return
# previously discovered, just update state with latest info
if node_id in devices:
for entity in devices[node_id]:
entity.update_ha_state(True)
return
# First time we see this node, detect sensors
for child in gateway.sensors[node_id].children.values():
name = '{} {}.{}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id)
for value_type in child.values.keys():
if child.type not in s_types or value_type not in v_types:
continue
devices[node_id].append(
entity_class(gateway, node_id, child.id, name, value_type))
if devices[node_id]:
_LOGGER.info('adding new devices: %s', devices[node_id])
add_devices(devices[node_id])
for entity in devices[node_id]:
entity.update_ha_state(True)
return mysensors_callback
class GatewayWrapper(object):
"""Gateway wrapper class, by subclassing serial gateway."""
def __init__(self, gateway, version):
"""Setup class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
version (str): Version of mysensors API.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
version (str): Version of mysensors API.
platform_callbacks (list): Callback functions, one per platform.
const (module): Mysensors API constants.
__initialised (bool): True if GatewayWrapper is initialised.
"""
self._wrapped_gateway = gateway
self.version = version
self.platform_callbacks = []
self.const = self.get_const()
self.__initialised = True
def __getattr__(self, name):
"""See if this object has attribute name."""
# Do not use hasattr, it goes into infinite recurrsion
if name in self.__dict__:
# this object has it
return getattr(self, name)
# proxy to the wrapped object
return getattr(self._wrapped_gateway, name)
def __setattr__(self, name, value):
"""See if this object has attribute name then set to value."""
if '_GatewayWrapper__initialised' not in self.__dict__:
return object.__setattr__(self, name, value)
elif name in self.__dict__:
object.__setattr__(self, name, value)
else:
object.__setattr__(self._wrapped_gateway, name, value)
def get_const(self):
"""Get mysensors API constants."""
if self.version == '1.5':
import mysensors.const_15 as const
else:
import mysensors.const_14 as const
return const
def callback_factory(self):
"""Return a new callback function."""
def node_update(update_type, node_id):
"""Callback for node updates from the MySensors gateway."""
_LOGGER.info('update %s: node %s', update_type, node_id)
for callback in self.platform_callbacks:
callback(self, node_id)
return node_update

View File

@ -0,0 +1,51 @@
"""
homeassistant.components.notify.free_mobile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Free Mobile SMS platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.free_mobile/
"""
import logging
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_USERNAME, CONF_ACCESS_TOKEN
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['freesms==0.1.0']
def get_service(hass, config):
""" Get the Free Mobile SMS notification service. """
if not validate_config({DOMAIN: config},
{DOMAIN: [CONF_USERNAME,
CONF_ACCESS_TOKEN]},
_LOGGER):
return None
return FreeSMSNotificationService(config[CONF_USERNAME],
config[CONF_ACCESS_TOKEN])
# pylint: disable=too-few-public-methods
class FreeSMSNotificationService(BaseNotificationService):
""" Implements notification service for the Free Mobile SMS service. """
def __init__(self, username, access_token):
from freesms import FreeClient
self.free_client = FreeClient(username, access_token)
def send_message(self, message="", **kwargs):
""" Send a message to the Free Mobile user cell. """
resp = self.free_client.send_sms(message)
if resp.status_code == 400:
_LOGGER.error("At least one parameter is missing")
elif resp.status_code == 402:
_LOGGER.error("Too much SMS send in a few time")
elif resp.status_code == 403:
_LOGGER.error("Wrong Username/Password")
elif resp.status_code == 500:
_LOGGER.error("Server error, try later")

View File

@ -7,59 +7,62 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.syslog/
"""
import logging
import syslog
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
FACILITIES = {'kernel': syslog.LOG_KERN,
'user': syslog.LOG_USER,
'mail': syslog.LOG_MAIL,
'daemon': syslog.LOG_DAEMON,
'auth': syslog.LOG_KERN,
'LPR': syslog.LOG_LPR,
'news': syslog.LOG_NEWS,
'uucp': syslog.LOG_UUCP,
'cron': syslog.LOG_CRON,
'syslog': syslog.LOG_SYSLOG,
'local0': syslog.LOG_LOCAL0,
'local1': syslog.LOG_LOCAL1,
'local2': syslog.LOG_LOCAL2,
'local3': syslog.LOG_LOCAL3,
'local4': syslog.LOG_LOCAL4,
'local5': syslog.LOG_LOCAL5,
'local6': syslog.LOG_LOCAL6,
'local7': syslog.LOG_LOCAL7}
OPTIONS = {'pid': syslog.LOG_PID,
'cons': syslog.LOG_CONS,
'ndelay': syslog.LOG_NDELAY,
'nowait': syslog.LOG_NOWAIT,
'perror': syslog.LOG_PERROR}
PRIORITIES = {5: syslog.LOG_EMERG,
4: syslog.LOG_ALERT,
3: syslog.LOG_CRIT,
2: syslog.LOG_ERR,
1: syslog.LOG_WARNING,
0: syslog.LOG_NOTICE,
-1: syslog.LOG_INFO,
-2: syslog.LOG_DEBUG}
def get_service(hass, config):
""" Get the mail notification service. """
"""Get the syslog notification service."""
if not validate_config({DOMAIN: config},
{DOMAIN: ['facility', 'option', 'priority']},
_LOGGER):
return None
_facility = FACILITIES.get(config['facility'], 40)
_option = OPTIONS.get(config['option'], 10)
_priority = PRIORITIES.get(config['priority'], -1)
import syslog
_facility = {
'kernel': syslog.LOG_KERN,
'user': syslog.LOG_USER,
'mail': syslog.LOG_MAIL,
'daemon': syslog.LOG_DAEMON,
'auth': syslog.LOG_KERN,
'LPR': syslog.LOG_LPR,
'news': syslog.LOG_NEWS,
'uucp': syslog.LOG_UUCP,
'cron': syslog.LOG_CRON,
'syslog': syslog.LOG_SYSLOG,
'local0': syslog.LOG_LOCAL0,
'local1': syslog.LOG_LOCAL1,
'local2': syslog.LOG_LOCAL2,
'local3': syslog.LOG_LOCAL3,
'local4': syslog.LOG_LOCAL4,
'local5': syslog.LOG_LOCAL5,
'local6': syslog.LOG_LOCAL6,
'local7': syslog.LOG_LOCAL7,
}.get(config['facility'], 40)
_option = {
'pid': syslog.LOG_PID,
'cons': syslog.LOG_CONS,
'ndelay': syslog.LOG_NDELAY,
'nowait': syslog.LOG_NOWAIT,
'perror': syslog.LOG_PERROR
}.get(config['option'], 10)
_priority = {
5: syslog.LOG_EMERG,
4: syslog.LOG_ALERT,
3: syslog.LOG_CRIT,
2: syslog.LOG_ERR,
1: syslog.LOG_WARNING,
0: syslog.LOG_NOTICE,
-1: syslog.LOG_INFO,
-2: syslog.LOG_DEBUG
}.get(config['priority'], -1)
return SyslogNotificationService(_facility, _option, _priority)
@ -76,6 +79,7 @@ class SyslogNotificationService(BaseNotificationService):
def send_message(self, message="", **kwargs):
""" Send a message to a user. """
import syslog
title = kwargs.get(ATTR_TITLE)

View File

@ -16,7 +16,7 @@ import json
import atexit
from homeassistant.core import Event, EventOrigin, State
import homeassistant.util.dt as date_util
import homeassistant.util.dt as dt_util
from homeassistant.remote import JSONEncoder
from homeassistant.const import (
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
@ -62,8 +62,8 @@ def row_to_state(row):
try:
return State(
row[1], row[2], json.loads(row[3]),
date_util.utc_from_timestamp(row[4]),
date_util.utc_from_timestamp(row[5]))
dt_util.utc_from_timestamp(row[4]),
dt_util.utc_from_timestamp(row[5]))
except ValueError:
# When json.loads fails
_LOGGER.exception("Error converting row to state: %s", row)
@ -74,7 +74,7 @@ def row_to_event(row):
""" Convert a databse row to an event. """
try:
return Event(row[1], json.loads(row[2]), EventOrigin(row[3]),
date_util.utc_from_timestamp(row[5]))
dt_util.utc_from_timestamp(row[5]))
except ValueError:
# When json.loads fails
_LOGGER.exception("Error converting row to event: %s", row)
@ -116,10 +116,10 @@ class RecorderRun(object):
self.start = _INSTANCE.recording_start
self.closed_incorrect = False
else:
self.start = date_util.utc_from_timestamp(row[1])
self.start = dt_util.utc_from_timestamp(row[1])
if row[2] is not None:
self.end = date_util.utc_from_timestamp(row[2])
self.end = dt_util.utc_from_timestamp(row[2])
self.closed_incorrect = bool(row[3])
@ -169,8 +169,8 @@ class Recorder(threading.Thread):
self.queue = queue.Queue()
self.quit_object = object()
self.lock = threading.Lock()
self.recording_start = date_util.utcnow()
self.utc_offset = date_util.now().utcoffset().total_seconds()
self.recording_start = dt_util.utcnow()
self.utc_offset = dt_util.now().utcoffset().total_seconds()
def start_recording(event):
""" Start recording. """
@ -217,10 +217,11 @@ class Recorder(threading.Thread):
def shutdown(self, event):
""" Tells the recorder to shut down. """
self.queue.put(self.quit_object)
self.block_till_done()
def record_state(self, entity_id, state, event_id):
""" Save a state to the database. """
now = date_util.utcnow()
now = dt_util.utcnow()
# State got deleted
if state is None:
@ -247,7 +248,7 @@ class Recorder(threading.Thread):
""" Save an event to the database. """
info = (
event.event_type, json.dumps(event.data, cls=JSONEncoder),
str(event.origin), date_util.utcnow(), event.time_fired,
str(event.origin), dt_util.utcnow(), event.time_fired,
self.utc_offset
)
@ -307,7 +308,7 @@ class Recorder(threading.Thread):
def save_migration(migration_id):
""" Save and commit a migration to the database. """
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
(migration_id, date_util.utcnow()))
(migration_id, dt_util.utcnow()))
self.conn.commit()
_LOGGER.info("Database migrated to version %d", migration_id)
@ -420,18 +421,18 @@ class Recorder(threading.Thread):
self.query(
"""INSERT INTO recorder_runs (start, created, utc_offset)
VALUES (?, ?, ?)""",
(self.recording_start, date_util.utcnow(), self.utc_offset))
(self.recording_start, dt_util.utcnow(), self.utc_offset))
def _close_run(self):
""" Save end time for current run. """
self.query(
"UPDATE recorder_runs SET end=? WHERE start=?",
(date_util.utcnow(), self.recording_start))
(dt_util.utcnow(), self.recording_start))
def _adapt_datetime(datetimestamp):
""" Turn a datetime into an integer for in the DB. """
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
def _verify_instance():

View File

@ -0,0 +1,69 @@
"""
homeassistant.components.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to control the GPIO pins of a Raspberry Pi.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rpi_gpio/
"""
import logging
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['RPi.GPIO==0.6.1']
DOMAIN = "rpi_gpio"
_LOGGER = logging.getLogger(__name__)
# pylint: disable=no-member
def setup(hass, config):
""" Sets up the Raspberry PI GPIO component. """
if GPIO is None:
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
return False
def cleanup_gpio(event):
""" Stuff to do before stop home assistant. """
GPIO.cleanup()
def prepare_gpio(event):
""" Stuff to do when home assistant starts. """
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
GPIO.setmode(GPIO.BCM)
return True
def setup_output(port):
""" Setup a GPIO as output. """
GPIO.setup(port, GPIO.OUT)
def setup_input(port, pull_mode):
""" Setup a GPIO as input. """
GPIO.setup(port, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP)
def write_output(port, value):
""" Write a value to a GPIO. """
GPIO.output(port, value)
def read_input(port):
""" Read a value from a GPIO. """
return GPIO.input(port)
def edge_detect(port, event_callback, bounce):
""" Adds detection for RISING and FALLING events. """
GPIO.add_event_detect(
port,
GPIO.BOTH,
callback=event_callback,
bouncetime=bounce)

View File

@ -73,8 +73,9 @@ def _process_config(scene_config):
for entity_id in c_entities:
if isinstance(c_entities[entity_id], dict):
state = c_entities[entity_id].pop('state', None)
attributes = c_entities[entity_id]
entity_attrs = c_entities[entity_id].copy()
state = entity_attrs.pop('state', None)
attributes = entity_attrs
else:
state = c_entities[entity_id]
attributes = {}

View File

@ -81,7 +81,7 @@ def setup(hass, config):
object_id)
continue
alias = cfg.get(CONF_ALIAS, object_id)
script = Script(hass, object_id, alias, cfg[CONF_SEQUENCE])
script = Script(object_id, alias, cfg[CONF_SEQUENCE])
component.add_entities((script,))
hass.services.register(DOMAIN, object_id, service_handler)
@ -106,8 +106,7 @@ def setup(hass, config):
class Script(ToggleEntity):
""" Represents a script. """
# pylint: disable=too-many-instance-attributes
def __init__(self, hass, object_id, name, sequence):
self.hass = hass
def __init__(self, object_id, name, sequence):
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self.sequence = sequence

View File

@ -9,7 +9,8 @@ https://home-assistant.io/components/sensor/
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import wink, zwave, isy994, verisure, ecobee
from homeassistant.components import (
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors)
DOMAIN = 'sensor'
SCAN_INTERVAL = 30
@ -22,7 +23,9 @@ DISCOVERY_PLATFORMS = {
zwave.DISCOVER_SENSORS: 'zwave',
isy994.DISCOVER_SENSORS: 'isy994',
verisure.DISCOVER_SENSORS: 'verisure',
ecobee.DISCOVER_SENSORS: 'ecobee'
ecobee.DISCOVER_SENSORS: 'ecobee',
tellduslive.DISCOVER_SENSORS: 'tellduslive',
mysensors.DISCOVER_SENSORS: 'mysensors',
}

View File

@ -11,8 +11,8 @@ import logging
import requests
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, \
DEVICE_DEFAULT_NAME
from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
DEVICE_DEFAULT_NAME)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.util import template, Throttle

View File

@ -53,6 +53,11 @@ class EliqSensor(Entity):
""" Returns the name. """
return self._name
@property
def icon(self):
""" Returns icon. """
return "mdi:speedometer"
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
@ -65,5 +70,8 @@ class EliqSensor(Entity):
def update(self):
""" Gets the latest data. """
response = self.api.get_data_now(channelid=self.channel_id)
self._state = int(response.power)
try:
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

@ -7,150 +7,177 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import logging
from collections import defaultdict
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP,
ATTR_BATTERY_LEVEL,
TEMP_CELCIUS, TEMP_FAHRENHEIT,
STATE_ON, STATE_OFF)
CONF_PORT = "port"
CONF_DEBUG = "debug"
CONF_PERSISTENCE = "persistence"
CONF_PERSISTENCE_FILE = "persistence_file"
CONF_VERSION = "version"
ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
'd4b809c2167650691058d1e29bfd2c4b1792b4b0.zip'
'#pymysensors==0.3']
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform. """
"""Setup the mysensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
import mysensors.mysensors as mysensors
import mysensors.const_14 as const
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states.
s_types = [
gateway.const.Presentation.S_TEMP,
gateway.const.Presentation.S_HUM,
gateway.const.Presentation.S_BARO,
gateway.const.Presentation.S_WIND,
gateway.const.Presentation.S_RAIN,
gateway.const.Presentation.S_UV,
gateway.const.Presentation.S_WEIGHT,
gateway.const.Presentation.S_POWER,
gateway.const.Presentation.S_DISTANCE,
gateway.const.Presentation.S_LIGHT_LEVEL,
gateway.const.Presentation.S_IR,
gateway.const.Presentation.S_WATER,
gateway.const.Presentation.S_AIR_QUALITY,
gateway.const.Presentation.S_CUSTOM,
gateway.const.Presentation.S_DUST,
gateway.const.Presentation.S_SCENE_CONTROLLER,
]
not_v_types = [
gateway.const.SetReq.V_ARMED,
gateway.const.SetReq.V_LIGHT,
gateway.const.SetReq.V_LOCK_STATUS,
]
if float(gateway.version) >= 1.5:
s_types.extend([
gateway.const.Presentation.S_COLOR_SENSOR,
gateway.const.Presentation.S_MULTIMETER,
])
not_v_types.extend([gateway.const.SetReq.V_STATUS, ])
v_types = [member for member in gateway.const.SetReq
if member.value not in not_v_types]
devices = {} # keep track of devices added to HA
# Just assume celcius means that the user wants metric for now.
# It may make more sense to make this a global config option in the future.
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def sensor_update(update_type, nid):
""" Callback for sensor updates from the MySensors gateway. """
_LOGGER.info("sensor_update %s: node %s", update_type, nid)
sensor = gateway.sensors[nid]
if sensor.sketch_name is None:
return
if nid not in devices:
devices[nid] = {}
node = devices[nid]
new_devices = []
for child_id, child in sensor.children.items():
if child_id not in node:
node[child_id] = {}
for value_type, value in child.values.items():
if value_type not in node[child_id]:
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
node[child_id][value_type] = \
MySensorsNodeValue(
nid, child_id, name, value_type, is_metric, const)
new_devices.append(node[child_id][value_type])
else:
node[child_id][value_type].update_sensor(
value, sensor.battery_level)
if new_devices:
_LOGGER.info("adding new devices: %s", new_devices)
add_devices(new_devices)
port = config.get(CONF_PORT)
if port is None:
_LOGGER.error("Missing required key 'port'")
return False
persistence = config.get(CONF_PERSISTENCE, True)
persistence_file = config.get(CONF_PERSISTENCE_FILE,
hass.config.path('mysensors.pickle'))
version = config.get(CONF_VERSION, '1.4')
gateway = mysensors.SerialGateway(port, sensor_update,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
gateway.metric = is_metric
gateway.debug = config.get(CONF_DEBUG, False)
gateway.start()
if persistence:
for nid in gateway.sensors:
sensor_update('sensor_update', nid)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
devices = defaultdict(list)
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
s_types, v_types, devices, add_devices, MySensorsSensor))
class MySensorsNodeValue(Entity):
""" Represents the value of a MySensors child node. """
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, node_id, child_id, name, value_type, metric, const):
self._name = name
class MySensorsSensor(Entity):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Setup class attributes on instantiation.
Args:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
Attributes:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
_name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
battery_level (int): Node battery level.
_values (dict): Child values. Non state values set as state attributes.
"""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self.battery_level = 0
self._name = name
self.value_type = value_type
self.metric = metric
self._value = ''
self.const = const
self.battery_level = 0
self._values = {}
@property
def should_poll(self):
""" MySensor gateway pushes its state to HA. """
"""MySensor gateway pushes its state to HA."""
return False
@property
def name(self):
""" The name of this sensor. """
"""The name of this entity."""
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._value
"""Return the state of the device."""
if not self._values:
return ''
return self._values[self.value_type]
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
if self.value_type == self.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
elif self.value_type == self.const.SetReq.V_HUM or \
self.value_type == self.const.SetReq.V_DIMMER or \
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
"""Unit of measurement of this entity."""
# pylint:disable=too-many-return-statements
if self.value_type == self.gateway.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.gateway.metric else TEMP_FAHRENHEIT
elif self.value_type == self.gateway.const.SetReq.V_HUM or \
self.value_type == self.gateway.const.SetReq.V_DIMMER or \
self.value_type == self.gateway.const.SetReq.V_PERCENTAGE or \
self.value_type == self.gateway.const.SetReq.V_LIGHT_LEVEL:
return '%'
elif self.value_type == self.gateway.const.SetReq.V_WATT:
return 'W'
elif self.value_type == self.gateway.const.SetReq.V_KWH:
return 'kWh'
elif self.value_type == self.gateway.const.SetReq.V_VOLTAGE:
return 'V'
elif self.value_type == self.gateway.const.SetReq.V_CURRENT:
return 'A'
elif self.value_type == self.gateway.const.SetReq.V_IMPEDANCE:
return 'ohm'
elif self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
return self._values[self.gateway.const.SetReq.V_UNIT_PREFIX]
return None
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_NODE_ID: self.node_id,
ATTR_CHILD_ID: self.child_id,
"""Return the state attributes."""
data = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
def update_sensor(self, value, battery_level):
""" Update a sensor with the latest value from the controller. """
_LOGGER.info("%s value = %s", self._name, value)
if self.value_type == self.const.SetReq.V_TRIPPED or \
self.value_type == self.const.SetReq.V_ARMED:
self._value = STATE_ON if int(value) == 1 else STATE_OFF
else:
self._value = value
self.battery_level = battery_level
self.update_ha_state()
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
def update(self):
"""Update the controller with the latest values from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_TRIPPED:
self._values[value_type] = STATE_ON if int(
value) == 1 else STATE_OFF
else:
self._values[value_type] = value
self.battery_level = node.battery_level

View File

@ -0,0 +1,154 @@
"""
homeassistant.components.sensor.netatmo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NetAtmo Weather Service service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.netatmo/
"""
import logging
from datetime import timedelta
from homeassistant.components.sensor import DOMAIN
from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD,
TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
REQUIREMENTS = [
'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/'
'43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip'
'#lnetatmo==0.4.0']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_CELCIUS],
'co2': ['CO2', 'ppm'],
'pressure': ['Pressure', 'mbar'],
'noise': ['Noise', 'dB'],
'humidity': ['Humidity', '%']
}
CONF_SECRET_KEY = 'secret_key'
ATTR_MODULE = 'modules'
# Return cached results if last scan was less then this time ago
# NetAtmo Data is uploaded to server every 10mn
# so this time should not be under
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the NetAtmo sensor. """
if not validate_config({DOMAIN: config},
{DOMAIN: [CONF_API_KEY,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SECRET_KEY]},
_LOGGER):
return None
import lnetatmo
authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None),
config.get(CONF_SECRET_KEY, None),
config.get(CONF_USERNAME, None),
config.get(CONF_PASSWORD, None))
if not authorization:
_LOGGER.error(
"Connection error "
"Please check your settings for NatAtmo API.")
return False
data = NetAtmoData(authorization)
dev = []
try:
# Iterate each module
for module_name, monitored_conditions in config[ATTR_MODULE].items():
# Test if module exist """
if module_name not in data.get_module_names():
_LOGGER.error('Module name: "%s" not found', module_name)
continue
# Only create sensor for monitored """
for variable in monitored_conditions:
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(
NetAtmoSensor(data, module_name, variable))
except KeyError:
pass
add_devices(dev)
# pylint: disable=too-few-public-methods
class NetAtmoSensor(Entity):
""" Implements a NetAtmo sensor. """
def __init__(self, netatmo_data, module_name, sensor_type):
self._name = "NetAtmo {} {}".format(module_name,
SENSOR_TYPES[sensor_type][0])
self.netatmo_data = netatmo_data
self.module_name = module_name
self.type = sensor_type
self._state = None
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._unit_of_measurement
# pylint: disable=too-many-branches
def update(self):
""" Gets the latest data from NetAtmo API and updates the states. """
self.netatmo_data.update()
data = self.netatmo_data.data[self.module_name]
if self.type == 'temperature':
self._state = round(data['Temperature'], 1)
elif self.type == 'humidity':
self._state = data['Humidity']
elif self.type == 'noise':
self._state = data['Noise']
elif self.type == 'co2':
self._state = data['CO2']
elif self.type == 'pressure':
self._state = round(data['Pressure'], 1)
class NetAtmoData(object):
""" Gets the latest data from NetAtmo. """
def __init__(self, auth):
self.auth = auth
self.data = None
def get_module_names(self):
""" Return all module available on the API as a list. """
self.update()
return self.data.keys()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Call the NetAtmo API to update the data. """
import lnetatmo
# Gets the latest data from NetAtmo. """
dev_list = lnetatmo.DeviceList(self.auth)
self.data = dev_list.lastData(exclude=3600)

View File

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

View File

@ -10,7 +10,7 @@ from datetime import timedelta
import logging
import requests
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.const import (CONF_VALUE_TEMPLATE, STATE_UNKNOWN)
from homeassistant.util import template, Throttle
from homeassistant.helpers.entity import Entity
@ -26,48 +26,21 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the REST sensor. """
use_get = False
use_post = False
resource = config.get('resource', None)
method = config.get('method', DEFAULT_METHOD)
payload = config.get('payload', None)
verify_ssl = config.get('verify_ssl', True)
if method == 'GET':
use_get = True
elif method == 'POST':
use_post = True
rest = RestData(method, resource, payload, verify_ssl)
rest.update()
try:
if use_get:
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:// to your URL.')
return False
except requests.exceptions.ConnectionError:
_LOGGER.error('No route to resource/endpoint. '
'Please check the URL in the configuration file.')
if rest.data is None:
_LOGGER.error('Unable to fetch Rest data')
return False
if use_get:
rest = RestDataGet(resource, verify_ssl)
elif use_post:
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))])
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
@ -78,7 +51,7 @@ class RestSensor(Entity):
self._hass = hass
self.rest = rest
self._name = name
self._state = 'n/a'
self._state = STATE_UNKNOWN
self._unit_of_measurement = unit_of_measurement
self._value_template = value_template
self.update()
@ -103,57 +76,33 @@ class RestSensor(Entity):
self.rest.update()
value = self.rest.data
if 'error' in value:
self._state = value['error']
else:
if self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, 'N/A')
self._state = value
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, STATE_UNKNOWN)
self._state = value
# pylint: disable=too-few-public-methods
class RestDataGet(object):
""" Class for handling the data retrieval with GET method. """
class RestData(object):
"""Class for handling the data retrieval."""
def __init__(self, resource, verify_ssl):
self._resource = resource
def __init__(self, method, resource, data, verify_ssl):
self._request = requests.Request(method, resource, data=data).prepare()
self._verify_ssl = verify_ssl
self.data = dict()
self.data = None
@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)
if 'error' in self.data:
del self.data['error']
with requests.Session() as sess:
response = sess.send(self._request, timeout=10,
verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint.")
self.data['error'] = 'N/A'
# 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 = dict()
@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)
if 'error' in self.data:
del self.data['error']
self.data = response.text
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint.")
self.data['error'] = 'N/A'
except requests.exceptions.RequestException:
_LOGGER.error("Error fetching data: %s", self._request)
self.data = None

View File

@ -1,97 +0,0 @@
"""
homeassistant.components.sensor.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a binary state sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rpi_gpio/
"""
# pylint: disable=import-error
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import (DEVICE_DEFAULT_NAME,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
DEFAULT_PULL_MODE = "UP"
DEFAULT_VALUE_HIGH = "HIGH"
DEFAULT_VALUE_LOW = "LOW"
DEFAULT_BOUNCETIME = 50
REQUIREMENTS = ['RPi.GPIO==0.5.11']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Raspberry PI GPIO ports. """
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
sensors = []
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
value_high = config.get('value_high', DEFAULT_VALUE_HIGH)
value_low = config.get('value_low', DEFAULT_VALUE_LOW)
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
ports = config.get('ports')
for port_num, port_name in ports.items():
sensors.append(RPiGPIOSensor(
port_name, port_num, pull_mode,
value_high, value_low, bouncetime))
add_devices(sensors)
def cleanup_gpio(event):
""" Stuff to do before stop home assistant. """
# pylint: disable=no-member
GPIO.cleanup()
def prepare_gpio(event):
""" Stuff to do when home assistant starts. """
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
# pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOSensor(Entity):
""" Sets up the Raspberry PI GPIO ports. """
def __init__(self, port_name, port_num, pull_mode,
value_high, value_low, bouncetime):
# pylint: disable=no-member
import RPi.GPIO as GPIO
self._name = port_name or DEVICE_DEFAULT_NAME
self._port = port_num
self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP
self._vhigh = value_high
self._vlow = value_low
self._bouncetime = bouncetime
GPIO.setup(self._port, GPIO.IN, pull_up_down=self._pull)
self._state = self._vhigh if GPIO.input(self._port) else self._vlow
def edge_callback(channel):
""" port changed state """
# pylint: disable=no-member
self._state = self._vhigh if GPIO.input(channel) else self._vlow
self.update_ha_state()
GPIO.add_event_detect(
self._port,
GPIO.BOTH,
callback=edge_callback,
bouncetime=self._bouncetime)
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def state(self):
""" Returns the state of the entity. """
return self._state

View File

@ -14,25 +14,25 @@ from homeassistant.const import STATE_ON, STATE_OFF
REQUIREMENTS = ['psutil==3.2.2']
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],
'disk_free': ['Disk Free', 'GiB'],
'memory_use_percent': ['RAM Use', '%'],
'memory_use': ['RAM Use', 'MiB'],
'memory_free': ['RAM Free', 'MiB'],
'processor_use': ['CPU Use', '%'],
'process': ['Process', ''],
'swap_use_percent': ['Swap Use', '%'],
'swap_use': ['Swap Use', 'GiB'],
'swap_free': ['Swap Free', 'GiB'],
'network_out': ['Sent', 'MiB'],
'network_in': ['Recieved', 'MiB'],
'packets_out': ['Packets sent', ''],
'packets_in': ['Packets recieved', ''],
'ipv4_address': ['IPv4 address', ''],
'ipv6_address': ['IPv6 address', ''],
'last_boot': ['Last Boot', ''],
'since_last_boot': ['Since Last Boot', '']
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
'disk_free': ['Disk Free', 'GiB', 'mdi:harddisk'],
'memory_use_percent': ['RAM Use', '%', 'mdi:memory'],
'memory_use': ['RAM Use', 'MiB', 'mdi:memory'],
'memory_free': ['RAM Free', 'MiB', 'mdi:memory'],
'processor_use': ['CPU Use', '%', 'mdi:memory'],
'process': ['Process', '', 'mdi:memory'],
'swap_use_percent': ['Swap Use', '%', 'mdi:harddisk'],
'swap_use': ['Swap Use', 'GiB', 'mdi:harddisk'],
'swap_free': ['Swap Free', 'GiB', 'mdi:harddisk'],
'network_out': ['Sent', 'MiB', 'mdi:server-network'],
'network_in': ['Recieved', 'MiB', 'mdi:server-network'],
'packets_out': ['Packets sent', '', 'mdi:server-network'],
'packets_in': ['Packets recieved', '', 'mdi:server-network'],
'ipv4_address': ['IPv4 address', '', 'mdi:server-network'],
'ipv6_address': ['IPv6 address', '', 'mdi:server-network'],
'last_boot': ['Last Boot', '', 'mdi:clock'],
'since_last_boot': ['Since Last Boot', '', 'mdi:clock']
}
_LOGGER = logging.getLogger(__name__)
@ -69,6 +69,10 @@ class SystemMonitorSensor(Entity):
def name(self):
return self._name.rstrip()
@property
def icon(self):
return SENSOR_TYPES[self.type][2]
@property
def state(self):
""" Returns the state of the device. """

View File

@ -0,0 +1,111 @@
"""
homeassistant.components.sensor.tellduslive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from Tellstick Net/Telstick Live.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.tellduslive/
"""
import logging
from datetime import datetime
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
from homeassistant.helpers.entity import Entity
from homeassistant.components import tellduslive
ATTR_LAST_UPDATED = "time_last_updated"
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['tellduslive']
SENSOR_TYPE_TEMP = "temp"
SENSOR_TYPE_HUMIDITY = "humidity"
SENSOR_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_TYPE_TEMP: ['Temperature', TEMP_CELCIUS, "mdi:thermometer"],
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', ""],
}
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up Tellstick sensors. """
sensors = tellduslive.NETWORK.get_sensors()
devices = []
for component in sensors:
for sensor in component["data"]:
# one component can have more than one sensor
# (e.g. both humidity and temperature)
devices.append(TelldusLiveSensor(component["id"],
component["name"],
sensor["name"]))
add_devices(devices)
class TelldusLiveSensor(Entity):
""" Represents a Telldus Live sensor. """
def __init__(self, sensor_id, sensor_name, sensor_type):
self._sensor_id = sensor_id
self._sensor_type = sensor_type
self._state = None
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
self._last_update = None
self._battery_level = None
self.update()
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def state_attributes(self):
attrs = dict()
if self._battery_level is not None:
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
if self._last_update is not None:
attrs[ATTR_LAST_UPDATED] = self._last_update
return attrs
@property
def unit_of_measurement(self):
return SENSOR_TYPES[self._sensor_type][1]
@property
def icon(self):
return SENSOR_TYPES[self._sensor_type][2]
def update(self):
values = tellduslive.NETWORK.get_sensor_value(self._sensor_id,
self._sensor_type)
self._state, self._battery_level, self._last_update = values
self._state = float(self._state)
if self._sensor_type == SENSOR_TYPE_TEMP:
self._state = round(self._state, 1)
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
self._state = int(round(self._state))
self._battery_level = round(self._battery_level * 100 / 255) # percent
self._last_update = str(datetime.fromtimestamp(self._last_update))

View File

@ -13,11 +13,9 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
TEMP_CELCIUS, TEMP_FAHRENHEIT)
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
REQUIREMENTS = ['pyvera==0.2.7']
_LOGGER = logging.getLogger(__name__)
@ -37,8 +35,20 @@ def get_devices(hass, config):
device_data = config.get('device_data', {})
vera_controller = veraApi.VeraController(base_url)
categories = ['Temperature Sensor', 'Light Sensor', 'Sensor']
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',
'Humidity Sensor',
'Sensor']
devices = []
try:
devices = vera_controller.get_devices(categories)
@ -49,11 +59,12 @@ def get_devices(hass, config):
vera_sensors = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_sensors.append(VeraSensor(device, extra_data))
vera_sensors.append(
VeraSensor(device, vera_controller, extra_data))
return vera_sensors
@ -66,8 +77,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VeraSensor(Entity):
""" Represents a Vera Sensor. """
def __init__(self, vera_device, extra_data=None):
def __init__(self, vera_device, controller, extra_data=None):
self.vera_device = vera_device
self.controller = controller
self.extra_data = extra_data
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
@ -76,8 +88,15 @@ class VeraSensor(Entity):
self.current_value = ''
self._temperature_units = None
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
""" Called by the vera device callback to update state. """
self.update_ha_state(True)
def __str__(self):
return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state)
return "%s %s %s" % (self.name, self.vera_device.device_id, self.state)
@property
def state(self):
@ -91,7 +110,12 @@ class VeraSensor(Entity):
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._temperature_units
if self.vera_device.category == "Temperature Sensor":
return self._temperature_units
elif self.vera_device.category == "Light Sensor":
return 'lux'
elif self.vera_device.category == "Humidity Sensor":
return '%'
@property
def state_attributes(self):
@ -100,28 +124,33 @@ class VeraSensor(Entity):
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = 'True' if armed else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
last_tripped = self.vera_device.last_trip
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.refresh_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
def update(self):
if self.vera_device.category == "Temperature Sensor":
self.vera_device.refresh_value('CurrentTemperature')
current_temp = self.vera_device.get_value('CurrentTemperature')
vera_temp_units = self.vera_device.veraController.temperature_units
current_temp = self.vera_device.temperature
vera_temp_units = (
self.vera_device.vera_controller.temperature_units)
if vera_temp_units == 'F':
self._temperature_units = TEMP_FAHRENHEIT
@ -137,10 +166,11 @@ class VeraSensor(Entity):
self.current_value = current_temp
elif self.vera_device.category == "Light Sensor":
self.vera_device.refresh_value('CurrentLevel')
self.current_value = self.vera_device.get_value('CurrentLevel')
self.current_value = self.vera_device.light
elif self.vera_device.category == "Humidity Sensor":
self.current_value = self.vera_device.humidity
elif self.vera_device.category == "Sensor":
tripped = self.vera_device.refresh_value('Tripped')
self.current_value = 'Tripped' if tripped == '1' else 'Not Tripped'
tripped = self.vera_device.is_tripped
self.current_value = 'Tripped' if tripped else 'Not Tripped'
else:
self.current_value = 'Unknown'

View File

@ -27,14 +27,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors.extend([
VerisureThermometer(value)
for value in verisure.get_climate_status().values()
for value in verisure.CLIMATE_STATUS.values()
if verisure.SHOW_THERMOMETERS and
hasattr(value, 'temperature') and value.temperature
])
sensors.extend([
VerisureHygrometer(value)
for value in verisure.get_climate_status().values()
for value in verisure.CLIMATE_STATUS.values()
if verisure.SHOW_HYGROMETERS and
hasattr(value, 'humidity') and value.humidity
])
@ -47,20 +47,19 @@ class VerisureThermometer(Entity):
def __init__(self, climate_status):
self._id = climate_status.id
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
@property
def name(self):
""" Returns the name of the device. """
return '{} {}'.format(
verisure.STATUS[self._device][self._id].location,
verisure.CLIMATE_STATUS[self._id].location,
"Temperature")
@property
def state(self):
""" Returns the state of the device. """
# remove ° character
return verisure.STATUS[self._device][self._id].temperature[:-1]
return verisure.CLIMATE_STATUS[self._id].temperature[:-1]
@property
def unit_of_measurement(self):
@ -69,7 +68,7 @@ class VerisureThermometer(Entity):
def update(self):
''' update sensor '''
verisure.update()
verisure.update_climate()
class VerisureHygrometer(Entity):
@ -77,20 +76,19 @@ class VerisureHygrometer(Entity):
def __init__(self, climate_status):
self._id = climate_status.id
self._device = verisure.MY_PAGES.DEVICE_CLIMATE
@property
def name(self):
""" Returns the name of the device. """
return '{} {}'.format(
verisure.STATUS[self._device][self._id].location,
verisure.CLIMATE_STATUS[self._id].location,
"Humidity")
@property
def state(self):
""" Returns the state of the device. """
# remove % character
return verisure.STATUS[self._device][self._id].humidity[:-1]
return verisure.CLIMATE_STATUS[self._id].humidity[:-1]
@property
def unit_of_measurement(self):
@ -99,4 +97,4 @@ class VerisureHygrometer(Entity):
def update(self):
''' update sensor '''
verisure.update()
verisure.update_climate()

View File

@ -11,7 +11,7 @@ import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
REQUIREMENTS = ['python-wink==0.3.1']
REQUIREMENTS = ['python-wink==0.4.1']
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -0,0 +1,194 @@
"""
homeassistant.components.sensor.yr
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yr.no weather service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.yr/
"""
import logging
import requests
from homeassistant.const import ATTR_ENTITY_PICTURE
from homeassistant.helpers.entity import Entity
from homeassistant.util import location, dt as dt_util
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['xmltodict']
# Sensor types are defined like so:
SENSOR_TYPES = {
'symbol': ['Symbol', ''],
'precipitation': ['Condition', 'mm'],
'temperature': ['Temperature', '°C'],
'windSpeed': ['Wind speed', 'm/s'],
'pressure': ['Pressure', 'mbar'],
'windDirection': ['Wind direction', '°'],
'humidity': ['Humidity', '%'],
'fog': ['Fog', '%'],
'cloudiness': ['Cloudiness', '%'],
'lowClouds': ['Low clouds', '%'],
'mediumClouds': ['Medium clouds', '%'],
'highClouds': ['High clouds', '%'],
'dewpointTemperature': ['Dewpoint temperature', '°C'],
}
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the Yr.no sensor. """
if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False
elevation = config.get('elevation')
if elevation is None:
elevation = location.elevation(hass.config.latitude,
hass.config.longitude)
coordinates = dict(lat=hass.config.latitude,
lon=hass.config.longitude, msl=elevation)
weather = YrData(coordinates)
dev = []
if 'monitored_conditions' in config:
for variable in config['monitored_conditions']:
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(YrSensor(variable, weather))
# add symbol as default sensor
if len(dev) == 0:
dev.append(YrSensor("symbol", weather))
add_devices(dev)
# pylint: disable=too-many-instance-attributes
class YrSensor(Entity):
""" Implements an Yr.no sensor. """
def __init__(self, sensor_type, weather):
self.client_name = 'yr'
self._name = SENSOR_TYPES[sensor_type][0]
self.type = sensor_type
self._state = None
self._weather = weather
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
self._update = None
self.update()
@property
def name(self):
return '{} {}'.format(self.client_name, self._name)
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def state_attributes(self):
""" Returns state attributes. """
data = {
'about': "Weather forecast from yr.no, delivered by the"
" Norwegian Meteorological Institute and the NRK"
}
if self.type == 'symbol':
symbol_nr = self._state
data[ATTR_ENTITY_PICTURE] = \
"http://api.met.no/weatherapi/weathericon/1.1/" \
"?symbol={0};content_type=image/png".format(symbol_nr)
return data
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._unit_of_measurement
def update(self):
""" Gets the latest data from yr.no and updates the states. """
now = dt_util.utcnow()
# check if data should be updated
if self._update is not None and now <= self._update:
return
self._weather.update()
# find sensor
for time_entry in self._weather.data['product']['time']:
valid_from = dt_util.str_to_datetime(
time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ")
valid_to = dt_util.str_to_datetime(
time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ")
loc_data = time_entry['location']
if self.type not in loc_data or now >= valid_to:
continue
self._update = valid_to
if self.type == 'precipitation' and valid_from < now:
self._state = loc_data[self.type]['@value']
break
elif self.type == 'symbol' and valid_from < now:
self._state = loc_data[self.type]['@number']
break
elif self.type == ('temperature', 'pressure', 'humidity',
'dewpointTemperature'):
self._state = loc_data[self.type]['@value']
break
elif self.type == 'windSpeed':
self._state = loc_data[self.type]['@mps']
break
elif self.type == 'windDirection':
self._state = float(loc_data[self.type]['@deg'])
break
elif self.type in ('fog', 'cloudiness', 'lowClouds',
'mediumClouds', 'highClouds'):
self._state = loc_data[self.type]['@percent']
break
# pylint: disable=too-few-public-methods
class YrData(object):
""" Gets the latest data and updates the states. """
def __init__(self, coordinates):
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
self._nextrun = None
self.data = {}
self.update()
def update(self):
""" Gets the latest data from yr.no """
# check if new will be available
if self._nextrun is not None and dt_util.utcnow() <= self._nextrun:
return
try:
with requests.Session() as sess:
response = sess.get(self._url)
except requests.RequestException:
return
if response.status_code != 200:
return
data = response.text
import xmltodict
self.data = xmltodict.parse(data)['weatherdata']
model = self.data['meta']['model']
if '@nextrun' not in model:
model = model[0]
self._nextrun = dt_util.str_to_datetime(model['@nextrun'],
"%Y-%m-%dT%H:%M:%SZ")

View File

@ -74,6 +74,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value.type == zwave.TYPE_DECIMAL):
add_devices([ZWaveMultilevelSensor(value)])
elif value.command_class == zwave.COMMAND_CLASS_ALARM:
add_devices([ZWaveAlarmSensor(value)])
class ZWaveSensor(Entity):
""" Represents a Z-Wave sensor. """
@ -216,3 +219,19 @@ class ZWaveMultilevelSensor(ZWaveSensor):
return TEMP_FAHRENHEIT
else:
return unit
class ZWaveAlarmSensor(ZWaveSensor):
""" A Z-wave sensor that sends Alarm alerts
Examples include certain Multisensors that have motion and
vibration capabilities. Z-Wave defines various alarm types
such as Smoke, Flood, Burglar, CarbonMonoxide, etc.
This wraps these alarms and allows you to use them to
trigger things, etc.
COMMAND_CLASS_ALARM is what we get here.
"""
# Empty subclass for now. Allows for later customizations
pass

View File

@ -8,10 +8,9 @@ https://home-assistant.io/components/sun/
"""
import logging
from datetime import timedelta
import urllib
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.util import location as location_util, dt as dt_util
from homeassistant.helpers.event import (
track_point_in_utc_time, track_utc_time_change)
from homeassistant.helpers.entity import Entity
@ -19,7 +18,6 @@ from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"
ENTITY_ID_ELEVATION = "sun.elevation"
CONF_ELEVATION = 'elevation'
@ -34,21 +32,21 @@ _LOGGER = logging.getLogger(__name__)
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
return hass.states.is_state(entity_id, STATE_ABOVE_HORIZON)
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)
return dt_util.as_local(utc_next) if utc_next else None
def next_setting_utc(hass, entity_id=None):
""" Returns the UTC datetime object of the next sun setting. """
"""UTC datetime object of the next sun setting."""
entity_id = entity_id or ENTITY_ID
state = hass.states.get(ENTITY_ID)
@ -63,14 +61,14 @@ def next_setting_utc(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)
return dt_util.as_local(utc_next) if utc_next else None
def next_rising_utc(hass, entity_id=None):
""" Returns the UTC datetime object of the next sun rising. """
"""UTC datetime object of the next sun rising."""
entity_id = entity_id or ENTITY_ID
state = hass.states.get(ENTITY_ID)
@ -85,7 +83,7 @@ def next_rising_utc(hass, entity_id=None):
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):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False
@ -111,21 +109,13 @@ def setup(hass, config):
platform_config = config.get(DOMAIN, {})
elevation = platform_config.get(CONF_ELEVATION)
if elevation is None:
elevation = location_util.elevation(latitude, longitude)
from astral import Location, GoogleGeocoder
from astral import Location
location = Location(('', '', latitude, longitude, hass.config.time_zone,
elevation or 0))
if elevation is None:
google = GoogleGeocoder()
try:
google._get_elevation(location) # pylint: disable=protected-access
_LOGGER.info(
'Retrieved elevation from Google: %s', location.elevation)
except urllib.error.URLError:
# If no internet connection available etc.
pass
elevation))
sun = Sun(hass, location)
sun.point_in_time_listener(dt_util.utcnow())
@ -134,7 +124,7 @@ def setup(hass, config):
class Sun(Entity):
""" Represents the Sun. """
"""Represents the Sun."""
entity_id = ENTITY_ID
@ -167,12 +157,12 @@ class Sun(Entity):
@property
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)
@property
def solar_elevation(self):
""" Returns the angle the sun is above the horizon"""
"""Angle the sun is above the horizon."""
from astral import Astral
return Astral().solar_elevation(
dt_util.utcnow(),

View File

@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.components import (
group, discovery, wink, isy994, verisure, zwave)
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
DOMAIN = 'switch'
SCAN_INTERVAL = 30
@ -40,6 +40,8 @@ DISCOVERY_PLATFORMS = {
isy994.DISCOVER_SWITCHES: 'isy994',
verisure.DISCOVER_SWITCHES: 'verisure',
zwave.DISCOVER_SWITCHES: 'zwave',
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
mysensors.DISCOVER_SWITCHES: 'mysensors',
}
PROP_TO_ATTR = {

View File

@ -10,6 +10,8 @@ import logging
import subprocess
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.util import template
_LOGGER = logging.getLogger(__name__)
@ -24,20 +26,30 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
for dev_name, properties in switches.items():
devices.append(
CommandSwitch(
hass,
properties.get('name', dev_name),
properties.get('oncmd', 'true'),
properties.get('offcmd', 'true')))
properties.get('offcmd', 'true'),
properties.get('statecmd', False),
properties.get(CONF_VALUE_TEMPLATE, False)))
add_devices_callback(devices)
class CommandSwitch(SwitchDevice):
""" Represents a switch that can be togggled using shell commands. """
def __init__(self, name, command_on, command_off):
# pylint: disable=too-many-arguments
def __init__(self, hass, name, command_on, command_off,
command_state, value_template):
self._hass = hass
self._name = name
self._state = False
self._command_on = command_on
self._command_off = command_off
self._command_state = command_state
self._value_template = value_template
@staticmethod
def _switch(command):
@ -51,10 +63,27 @@ class CommandSwitch(SwitchDevice):
return success
@staticmethod
def _query_state_value(command):
""" Execute state command for return value. """
_LOGGER.info('Running state command: %s', command)
try:
return_value = subprocess.check_output(command, shell=True)
return return_value.strip().decode('utf-8')
except subprocess.CalledProcessError:
_LOGGER.error('Command failed: %s', command)
@staticmethod
def _query_state_code(command):
""" Execute state command for return code. """
_LOGGER.info('Running state command: %s', command)
return subprocess.call(command, shell=True) == 0
@property
def should_poll(self):
""" No polling needed. """
return False
""" Only poll if we have statecmd. """
return self._command_state is not None
@property
def name(self):
@ -66,14 +95,34 @@ class CommandSwitch(SwitchDevice):
""" True if device is on. """
return self._state
def _query_state(self):
""" Query for state. """
if not self._command_state:
_LOGGER.error('No state command specified')
return
if self._value_template:
return CommandSwitch._query_state_value(self._command_state)
return CommandSwitch._query_state_code(self._command_state)
def update(self):
""" Update device state. """
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
self._state = (payload.lower() == "true")
def turn_on(self, **kwargs):
""" Turn the device on. """
if CommandSwitch._switch(self._command_on):
if (CommandSwitch._switch(self._command_on) and
not self._command_state):
self._state = True
self.update_ha_state()
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the device off. """
if CommandSwitch._switch(self._command_off):
if (CommandSwitch._switch(self._command_off) and
not self._command_state):
self._state = False
self.update_ha_state()
self.update_ha_state()

View File

@ -0,0 +1,164 @@
"""
homeassistant.components.switch.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors.html
"""
import logging
from collections import defaultdict
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
STATE_ON, STATE_OFF)
import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for switches."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states.
s_types = [
gateway.const.Presentation.S_DOOR,
gateway.const.Presentation.S_MOTION,
gateway.const.Presentation.S_SMOKE,
gateway.const.Presentation.S_LIGHT,
gateway.const.Presentation.S_LOCK,
]
v_types = [
gateway.const.SetReq.V_ARMED,
gateway.const.SetReq.V_LIGHT,
gateway.const.SetReq.V_LOCK_STATUS,
]
if float(gateway.version) >= 1.5:
s_types.extend([
gateway.const.Presentation.S_BINARY,
gateway.const.Presentation.S_SPRINKLER,
gateway.const.Presentation.S_WATER_LEAK,
gateway.const.Presentation.S_SOUND,
gateway.const.Presentation.S_VIBRATION,
gateway.const.Presentation.S_MOISTURE,
])
v_types.extend([gateway.const.SetReq.V_STATUS, ])
devices = defaultdict(list)
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
s_types, v_types, devices, add_devices, MySensorsSwitch))
class MySensorsSwitch(SwitchDevice):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Setup class attributes on instantiation.
Args:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
Attributes:
gateway (GatewayWrapper): Gateway object
node_id (str): Id of node.
child_id (str): Id of child.
_name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
battery_level (int): Node battery level.
_values (dict): Child values. Non state values set as state attributes.
"""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self._name = name
self.value_type = value_type
self.battery_level = 0
self._values = {}
@property
def should_poll(self):
"""MySensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""The name of this entity."""
return self._name
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
"""Return the state attributes."""
data = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
@property
def is_on(self):
"""Return True if switch is on."""
if self.value_type in self._values:
return self._values[self.value_type] == STATE_ON
return False
def turn_on(self):
"""Turn the switch on."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 1)
self._values[self.value_type] = STATE_ON
self.update_ha_state()
def turn_off(self):
"""Turn the switch off."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 0)
self._values[self.value_type] = STATE_OFF
self.update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_ARMED or \
value_type == self.gateway.const.SetReq.V_STATUS or \
value_type == self.gateway.const.SetReq.V_LIGHT or \
value_type == self.gateway.const.SetReq.V_LOCK_STATUS:
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
else:
self._values[value_type] = value
self.battery_level = node.battery_level

View File

@ -18,7 +18,7 @@ DEFAULT_BODY_ON = "ON"
DEFAULT_BODY_OFF = "OFF"
# pylint: disable=unused-argument
# pylint: disable=unused-argument,
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Get REST switch. """
@ -32,11 +32,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
requests.get(resource, timeout=10)
except requests.exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.")
"Add http:// or https:// to your URL")
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to resource/endpoint. "
"Please check the IP address in the configuration file.")
_LOGGER.error("No route to resource/endpoint: %s", resource)
return False
add_devices_callback([RestSwitch(

View File

@ -13,8 +13,9 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.util import slugify
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.rfxtrx import ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, \
ATTR_NAME, EVENT_BUTTON_PRESSED
from homeassistant.components.rfxtrx import (
ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID,
ATTR_NAME, EVENT_BUTTON_PRESSED)
DEPENDENCIES = ['rfxtrx']

View File

@ -1,69 +1,48 @@
"""
homeassistant.components.switch.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to control the GPIO pins of a Raspberry Pi.
Allows to configure a switch using RPi GPIO.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.rpi_gpio/
"""
import logging
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (DEVICE_DEFAULT_NAME,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.const import (DEVICE_DEFAULT_NAME)
DEFAULT_INVERT_LOGIC = False
REQUIREMENTS = ['RPi.GPIO==0.5.11']
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Raspberry PI GPIO ports. """
if GPIO is None:
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
return
# pylint: disable=no-member
GPIO.setmode(GPIO.BCM)
""" Sets up the Raspberry PI GPIO devices. """
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
switches = []
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
ports = config.get('ports')
for port_num, port_name in ports.items():
switches.append(RPiGPIOSwitch(port_name, port_num, invert_logic))
for port, name in ports.items():
switches.append(RPiGPIOSwitch(name, port, invert_logic))
add_devices(switches)
def cleanup_gpio(event):
""" Stuff to do before stop home assistant. """
# pylint: disable=no-member
GPIO.cleanup()
def prepare_gpio(event):
""" Stuff to do when home assistant starts. """
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
class RPiGPIOSwitch(ToggleEntity):
""" Represents a port that can be toggled using Raspberry Pi GPIO. """
def __init__(self, name, gpio, invert_logic):
""" Represents a switch that can be toggled using Raspberry Pi GPIO. """
def __init__(self, name, port, invert_logic):
self._name = name or DEVICE_DEFAULT_NAME
self._gpio = gpio
self._active_state = not invert_logic
self._state = not self._active_state
# pylint: disable=no-member
GPIO.setup(gpio, GPIO.OUT)
self._port = port
self._invert_logic = invert_logic
self._state = False
rpi_gpio.setup_output(self._port)
@property
def name(self):
""" The name of the port. """
""" The name of the switch. """
return self._name
@property
@ -76,41 +55,14 @@ class RPiGPIOSwitch(ToggleEntity):
""" True if device is on. """
return self._state
def turn_on(self, **kwargs):
def turn_on(self):
""" Turn the device on. """
if self._switch(self._active_state):
self._state = True
rpi_gpio.write_output(self._port, 0 if self._invert_logic else 1)
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
def turn_off(self):
""" Turn the device off. """
if self._switch(not self._active_state):
self._state = False
rpi_gpio.write_output(self._port, 1 if self._invert_logic else 0)
self._state = False
self.update_ha_state()
def _switch(self, new_state):
""" Change the output value to Raspberry Pi GPIO port. """
_LOGGER.info('Setting GPIO %s to %s', self._gpio, new_state)
# pylint: disable=bare-except
try:
# pylint: disable=no-member
GPIO.output(self._gpio, 1 if new_state else 0)
except:
_LOGGER.error('GPIO "%s" output failed', self._gpio)
return False
return True
# pylint: disable=no-self-use
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
return None
@property
def state_attributes(self):
""" Returns optional state attributes. """
data = {}
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data

View File

@ -0,0 +1,73 @@
"""
homeassistant.components.switch.tellduslive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Tellstick switches using Tellstick Net and
the Telldus Live online service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.tellduslive/
"""
import logging
from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN)
from homeassistant.components import tellduslive
from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['tellduslive']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Find and return Tellstick switches. """
switches = tellduslive.NETWORK.get_switches()
add_devices([TelldusLiveSwitch(switch["name"],
switch["id"])
for switch in switches if switch["type"] == "device"])
class TelldusLiveSwitch(ToggleEntity):
""" Represents a Tellstick switch. """
def __init__(self, name, switch_id):
self._name = name
self._id = switch_id
self._state = STATE_UNKNOWN
self.update()
@property
def should_poll(self):
""" Tells Home Assistant to poll this entity. """
return False
@property
def name(self):
""" Returns the name of the switch if any. """
return self._name
def update(self):
from tellive.live import const
state = tellduslive.NETWORK.get_switch_state(self._id)
if state == const.TELLSTICK_TURNON:
self._state = STATE_ON
elif state == const.TELLSTICK_TURNOFF:
self._state = STATE_OFF
else:
self._state = STATE_UNKNOWN
@property
def is_on(self):
""" True if switch is on. """
return self._state == STATE_ON
def turn_on(self, **kwargs):
""" Turns the switch on. """
if tellduslive.NETWORK.turn_switch_on(self._id):
self._state = STATE_ON
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turns the switch off. """
if tellduslive.NETWORK.turn_switch_off(self._id):
self._state = STATE_OFF
self.update_ha_state()

View File

@ -7,17 +7,21 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.vera/
"""
import logging
import time
from requests.exceptions import RequestException
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME)
from homeassistant.components.switch import SwitchDevice
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
'#python-vera==0.1.1']
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_TRIPPED,
ATTR_ARMED,
ATTR_LAST_TRIP_TIME,
EVENT_HOMEASSISTANT_STOP,
STATE_ON,
STATE_OFF)
REQUIREMENTS = ['pyvera==0.2.7']
_LOGGER = logging.getLogger(__name__)
@ -37,7 +41,16 @@ def get_devices(hass, config):
device_data = config.get('device_data', {})
vera_controller = veraApi.VeraController(base_url)
vera_controller, created = veraApi.init_controller(base_url)
if created:
def stop_subscription(event):
""" Shutdown Vera subscriptions and subscription thread on exit"""
_LOGGER.info("Shutting down subscriptions.")
vera_controller.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
devices = []
try:
devices = vera_controller.get_devices([
@ -49,11 +62,12 @@ def get_devices(hass, config):
vera_switches = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
vera_switches.append(VeraSwitch(device, extra_data))
vera_switches.append(
VeraSwitch(device, vera_controller, extra_data))
return vera_switches
@ -63,19 +77,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(get_devices(hass, config))
class VeraSwitch(ToggleEntity):
class VeraSwitch(SwitchDevice):
""" Represents a Vera Switch. """
def __init__(self, vera_device, extra_data=None):
def __init__(self, vera_device, controller, extra_data=None):
self.vera_device = vera_device
self.extra_data = extra_data
self.controller = controller
if self.extra_data and self.extra_data.get('name'):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self.is_on_status = False
# for debouncing status check after command is sent
self.last_command_send = 0
self._state = STATE_OFF
self.controller.register(vera_device, self._update_callback)
self.update()
def _update_callback(self, _device):
self.update_ha_state(True)
@property
def name(self):
@ -90,44 +109,47 @@ class VeraSwitch(ToggleEntity):
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = 'True' if armed else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
last_tripped = self.vera_device.last_trip
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.refresh_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def turn_on(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_on()
self.is_on_status = True
self._state = STATE_ON
self.update_ha_state()
def turn_off(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_off()
self.is_on_status = False
self._state = STATE_OFF
self.update_ha_state()
@property
def should_poll(self):
""" Tells Home Assistant not to poll this entity. """
return False
@property
def is_on(self):
""" True if device is on. """
return self.is_on_status
return self._state == STATE_ON
def update(self):
# We need to debounce the status call after turning switch on or off
# because the vera has some lag in updating the device status
try:
if (self.last_command_send + 5) < time.time():
self.is_on_status = self.vera_device.is_switched_on()
except RequestException:
_LOGGER.warning('Could not update status for %s', self.name)
""" Called by the vera device callback to update state. """
if self.vera_device.is_switched_on():
self._state = STATE_ON
else:
self._state = STATE_OFF

View File

@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
switches.extend([
VerisureSmartplug(value)
for value in verisure.get_smartplug_status().values()
for value in verisure.SMARTPLUG_STATUS.values()
if verisure.SHOW_SMARTPLUGS
])
@ -36,31 +36,29 @@ class VerisureSmartplug(SwitchDevice):
""" Represents a Verisure smartplug. """
def __init__(self, smartplug_status):
self._id = smartplug_status.id
self.status_on = verisure.MY_PAGES.SMARTPLUG_ON
self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF
@property
def name(self):
""" Get the name (location) of the smartplug. """
return verisure.get_smartplug_status()[self._id].location
return verisure.SMARTPLUG_STATUS[self._id].location
@property
def is_on(self):
""" Returns True if on """
plug_status = verisure.get_smartplug_status()[self._id].status
return plug_status == self.status_on
plug_status = verisure.SMARTPLUG_STATUS[self._id].status
return plug_status == 'on'
def turn_on(self):
""" Set smartplug status on. """
verisure.MY_PAGES.set_smartplug_status(
self._id,
self.status_on)
verisure.MY_PAGES.smartplug.set(self._id, 'on')
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'on')
verisure.update_smartplug()
def turn_off(self):
""" Set smartplug status off. """
verisure.MY_PAGES.set_smartplug_status(
self._id,
self.status_off)
verisure.MY_PAGES.smartplug.set(self._id, 'off')
verisure.MY_PAGES.smartplug.wait_while_updating(self._id, 'off')
verisure.update_smartplug()
def update(self):
verisure.update()
verisure.update_smartplug()

View File

@ -9,11 +9,14 @@ https://home-assistant.io/components/switch.wemo/
import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pywemo==0.3.3']
REQUIREMENTS = ['pywemo==0.3.8']
_LOGGER = logging.getLogger(__name__)
_WEMO_SUBSCRIPTION_REGISTRY = None
# pylint: disable=unused-argument, too-many-function-args
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@ -21,6 +24,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
import pywemo
import pywemo.discovery as discovery
global _WEMO_SUBSCRIPTION_REGISTRY
if _WEMO_SUBSCRIPTION_REGISTRY is None:
_WEMO_SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry()
_WEMO_SUBSCRIPTION_REGISTRY.start()
def stop_wemo(event):
""" Shutdown Wemo subscriptions and subscription thread on exit"""
_LOGGER.info("Shutting down subscriptions.")
_WEMO_SUBSCRIPTION_REGISTRY.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)
if discovery_info is not None:
location = discovery_info[2]
mac = discovery_info[3]
@ -47,6 +62,22 @@ class WemoSwitch(SwitchDevice):
self.insight_params = None
self.maker_params = None
_WEMO_SUBSCRIPTION_REGISTRY.register(wemo)
_WEMO_SUBSCRIPTION_REGISTRY.on(
wemo, None, self._update_callback)
def _update_callback(self, _device, _params):
""" Called by the wemo device callback to update state. """
_LOGGER.info(
'Subscription update for %s',
_device)
self.update_ha_state(True)
@property
def should_poll(self):
""" No polling needed with subscriptions """
return False
@property
def unique_id(self):
""" Returns the id of this WeMo switch """

View File

@ -11,7 +11,7 @@ import logging
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.3.1']
REQUIREMENTS = ['python-wink==0.4.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -30,3 +30,5 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pywink.set_bearer_token(token)
add_devices(WinkToggleDevice(switch) for switch in pywink.get_switches())
add_devices(WinkToggleDevice(switch) for switch in
pywink.get_powerstrip_outlets())

View File

@ -0,0 +1,209 @@
"""
homeassistant.components.tellduslive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tellduslive Component
This component adds support for the Telldus Live service.
Telldus Live is the online service used with Tellstick Net devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.tellduslive/
Developer access to the Telldus Live service is neccessary
API keys can be aquired from https://api.telldus.com/keys/index
Tellstick Net devices can be auto discovered using the method described in:
https://developer.telldus.com/doxygen/html/TellStickNet.html
It might be possible to communicate with the Tellstick Net device
directly, bypassing the Tellstick Live service.
This however is poorly documented and yet not fully supported (?) according to
http://developer.telldus.se/ticket/114 and
https://developer.telldus.com/doxygen/html/TellStickNet.html
API requests to certain methods, as described in
https://api.telldus.com/explore/sensor/info
are limited to one request every 10 minutes
"""
from datetime import timedelta
import logging
from homeassistant.loader import get_component
from homeassistant import bootstrap
from homeassistant.util import Throttle
from homeassistant.helpers import validate_config
from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "tellduslive"
DISCOVER_SWITCHES = "tellduslive.switches"
DISCOVER_SENSORS = "tellduslive.sensors"
CONF_PUBLIC_KEY = "public_key"
CONF_PRIVATE_KEY = "private_key"
CONF_TOKEN = "token"
CONF_TOKEN_SECRET = "token_secret"
REQUIREMENTS = ['tellive-py==0.5.2']
_LOGGER = logging.getLogger(__name__)
NETWORK = None
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600)
class TelldusLiveData(object):
""" Gets the latest data and update the states. """
def __init__(self, hass, config):
public_key = config[DOMAIN].get(CONF_PUBLIC_KEY)
private_key = config[DOMAIN].get(CONF_PRIVATE_KEY)
token = config[DOMAIN].get(CONF_TOKEN)
token_secret = config[DOMAIN].get(CONF_TOKEN_SECRET)
from tellive.client import LiveClient
from tellive.live import TelldusLive
self._sensors = []
self._switches = []
self._client = LiveClient(public_key=public_key,
private_key=private_key,
access_token=token,
access_secret=token_secret)
self._api = TelldusLive(self._client)
def update(self, hass, config):
""" Send discovery event if component not yet discovered """
self._update_sensors()
self._update_switches()
for component_name, found_devices, discovery_type in \
(('sensor', self._sensors, DISCOVER_SENSORS),
('switch', self._switches, DISCOVER_SWITCHES)):
if len(found_devices):
component = get_component(component_name)
bootstrap.setup_component(hass, component.DOMAIN, config)
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
{ATTR_SERVICE: discovery_type,
ATTR_DISCOVERED: {}})
def _request(self, what, **params):
""" Sends a request to the tellstick live API """
from tellive.live import const
supported_methods = const.TELLSTICK_TURNON \
| const.TELLSTICK_TURNOFF \
| const.TELLSTICK_TOGGLE
default_params = {'supportedMethods': supported_methods,
"includeValues": 1,
"includeScale": 1}
params.update(default_params)
# room for improvement: the telllive library doesn't seem to
# re-use sessions, instead it opens a new session for each request
# this needs to be fixed
response = self._client.request(what, params)
return response
def check_request(self, what, **params):
""" Make request, check result if successful """
response = self._request(what, **params)
return response['status'] == "success"
def validate_session(self):
""" Make a dummy request to see if the session is valid """
try:
response = self._request("user/profile")
return 'email' in response
except RuntimeError:
return False
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_sensors(self):
""" Get the latest sensor data from Telldus Live """
_LOGGER.info("Updating sensors from Telldus Live")
self._sensors = self._request("sensors/list")["sensor"]
def _update_switches(self):
""" Get the configured switches from Telldus Live"""
_LOGGER.info("Updating switches from Telldus Live")
self._switches = self._request("devices/list")["device"]
# filter out any group of switches
self._switches = [switch for switch in self._switches
if switch["type"] == "device"]
def get_sensors(self):
""" Get the configured sensors """
self._update_sensors()
return self._sensors
def get_switches(self):
""" Get the configured switches """
self._update_switches()
return self._switches
def get_sensor_value(self, sensor_id, sensor_name):
""" Get the latest (possibly cached) sensor value """
self._update_sensors()
for component in self._sensors:
if component["id"] == sensor_id:
for sensor in component["data"]:
if sensor["name"] == sensor_name:
return (sensor["value"],
component["battery"],
component["lastUpdated"])
def get_switch_state(self, switch_id):
""" returns state of switch. """
_LOGGER.info("Updating switch state from Telldus Live")
response = self._request("device/info", id=switch_id)["state"]
return int(response)
def turn_switch_on(self, switch_id):
""" turn switch off """
return self.check_request("device/turnOn", id=switch_id)
def turn_switch_off(self, switch_id):
""" turn switch on """
return self.check_request("device/turnOff", id=switch_id)
def setup(hass, config):
""" Setup the tellduslive component """
# fixme: aquire app key and provide authentication
# using username + password
if not validate_config(config,
{DOMAIN: [CONF_PUBLIC_KEY,
CONF_PRIVATE_KEY,
CONF_TOKEN,
CONF_TOKEN_SECRET]},
_LOGGER):
_LOGGER.error(
"Configuration Error: "
"Please make sure you have configured your keys "
"that can be aquired from https://api.telldus.com/keys/index")
return False
global NETWORK
NETWORK = TelldusLiveData(hass, config)
if not NETWORK.validate_session():
_LOGGER.error(
"Authentication Error: "
"Please make sure you have configured your keys "
"that can be aquired from https://api.telldus.com/keys/index")
return False
NETWORK.update(hass, config)
return True

View File

@ -224,12 +224,12 @@ class ThermostatDevice(Entity):
@property
def min_temp(self):
""" Return minimum temperature. """
return convert(7, TEMP_CELCIUS, self.unit_of_measurement)
return round(convert(7, TEMP_CELCIUS, self.unit_of_measurement))
@property
def max_temp(self):
""" Return maxmum temperature. """
return convert(35, TEMP_CELCIUS, self.unit_of_measurement)
return round(convert(35, TEMP_CELCIUS, self.unit_of_measurement))
def _convert(self, temp, round_dec=None):
""" Convert temperature from this thermost into user preferred

View File

@ -46,8 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
data = ecobee.NETWORK
hold_temp = discovery_info['hold_temp']
_LOGGER.info("Loading ecobee thermostat component with hold_temp set to "
+ str(hold_temp))
_LOGGER.info(
"Loading ecobee thermostat component with hold_temp set to %s",
hold_temp)
add_devices(Thermostat(data, index, hold_temp)
for index in range(len(data.ecobee.thermostats)))

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
https://home-assistant.io/components/thermostat.honeywell/
"""
import socket
import logging
import socket
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
@ -15,6 +16,8 @@ REQUIREMENTS = ['evohomeclient==0.2.4']
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature"
# pylint: disable=unused-argument
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)
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:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
evo_api = EvohomeClient(username, password)
try:
zones = evo_api.temperatures(force_refresh=True)
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:
_LOGGER.error(
"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):
""" 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._current_temperature = None
self._target_temperature = None
@ -52,6 +65,8 @@ class RoundThermostat(ThermostatDevice):
self._id = zone_id
self._master = master
self._is_dhw = False
self._away_temp = away_temp
self._away = False
self.update()
@property
@ -80,6 +95,25 @@ class RoundThermostat(ThermostatDevice):
""" Set new target 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):
try:
# Only refresh if this is the "master" device,

View File

@ -0,0 +1,90 @@
"""
homeassistant.components.thermostat.proliphix
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Proliphix NT10e Thermostat is an ethernet connected thermostat.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/thermostat.proliphix/
"""
from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL,
STATE_IDLE, STATE_HEAT)
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
CONF_HOST, TEMP_FAHRENHEIT)
REQUIREMENTS = ['proliphix==0.1.0']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Proliphix thermostats. """
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
import proliphix
pdp = proliphix.PDP(host, username, password)
add_devices([
ProliphixThermostat(pdp)
])
class ProliphixThermostat(ThermostatDevice):
""" Represents a Proliphix thermostat. """
def __init__(self, pdp):
self._pdp = pdp
# initial data
self._pdp.update()
self._name = self._pdp.name
@property
def should_poll(self):
""" Polling needed for thermostat.. """
return True
def update(self):
""" Update the data from the thermostat. """
self._pdp.update()
@property
def name(self):
""" Returns the name of the thermostat. """
return self._name
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
return {
"fan": self._pdp.fan_state
}
@property
def unit_of_measurement(self):
""" Returns the unit of measurement. """
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
""" Returns the current temperature. """
return self._pdp.cur_temp
@property
def target_temperature(self):
""" Returns the temperature we try to reach. """
return self._pdp.setback_heat
@property
def operation(self):
""" Returns the current state of the thermostat. """
state = self._pdp.hvac_state
if state in (1, 2):
return STATE_IDLE
elif state == 3:
return STATE_HEAT
elif state == 6:
return STATE_COOL
def set_temperature(self, temperature):
""" Set new target temperature. """
self._pdp.setback_heat = temperature

View File

@ -7,6 +7,8 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/verisure/
"""
import logging
import time
from datetime import timedelta
from homeassistant import bootstrap
@ -26,15 +28,14 @@ DISCOVER_SWITCHES = 'verisure.switches'
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
DEPENDENCIES = ['alarm_control_panel']
REQUIREMENTS = [
'https://github.com/persandstrom/python-verisure/archive/'
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6'
]
REQUIREMENTS = ['vsure==0.4.5']
_LOGGER = logging.getLogger(__name__)
MY_PAGES = None
STATUS = {}
ALARM_STATUS = {}
SMARTPLUG_STATUS = {}
CLIMATE_STATUS = {}
VERISURE_LOGIN_ERROR = None
VERISURE_ERROR = None
@ -43,11 +44,12 @@ SHOW_THERMOMETERS = True
SHOW_HYGROMETERS = True
SHOW_ALARM = True
SHOW_SMARTPLUGS = True
CODE_DIGITS = 4
# if wrong password was given don't try again
WRONG_PASSWORD_GIVEN = False
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5)
MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=1)
def setup(hass, config):
@ -60,15 +62,13 @@ def setup(hass, config):
from verisure import MyPages, LoginError, Error
STATUS[MyPages.DEVICE_ALARM] = {}
STATUS[MyPages.DEVICE_CLIMATE] = {}
STATUS[MyPages.DEVICE_SMARTPLUG] = {}
global SHOW_THERMOMETERS, SHOW_HYGROMETERS, SHOW_ALARM, SHOW_SMARTPLUGS
global SHOW_THERMOMETERS, SHOW_HYGROMETERS,\
SHOW_ALARM, SHOW_SMARTPLUGS, CODE_DIGITS
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1'))
SHOW_ALARM = int(config[DOMAIN].get('alarm', '1'))
SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1'))
CODE_DIGITS = int(config[DOMAIN].get('code_digits', '4'))
global MY_PAGES
MY_PAGES = MyPages(
@ -84,7 +84,9 @@ def setup(hass, config):
_LOGGER.error('Could not log in to verisure mypages, %s', ex)
return False
update()
update_alarm()
update_climate()
update_smartplug()
# Load components for the devices in the ISY controller that we support
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
@ -101,24 +103,10 @@ def setup(hass, config):
return True
def get_alarm_status():
""" Return a list of status overviews for alarm components. """
return STATUS[MY_PAGES.DEVICE_ALARM]
def get_climate_status():
""" Return a list of status overviews for alarm components. """
return STATUS[MY_PAGES.DEVICE_CLIMATE]
def get_smartplug_status():
""" Return a list of status overviews for alarm components. """
return STATUS[MY_PAGES.DEVICE_SMARTPLUG]
def reconnect():
""" Reconnect to verisure mypages. """
try:
time.sleep(1)
MY_PAGES.login()
except VERISURE_LOGIN_ERROR as ex:
_LOGGER.error("Could not login to Verisure mypages, %s", ex)
@ -129,19 +117,31 @@ def reconnect():
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
def update():
def update_alarm():
""" Updates the status of alarms. """
update_component(MY_PAGES.alarm.get, ALARM_STATUS)
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
def update_climate():
""" Updates the status of climate sensors. """
update_component(MY_PAGES.climate.get, CLIMATE_STATUS)
@Throttle(MIN_TIME_BETWEEN_REQUESTS)
def update_smartplug():
""" Updates the status of smartplugs. """
update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS)
def update_component(get_function, status):
""" Updates the status of verisure components. """
if WRONG_PASSWORD_GIVEN:
_LOGGER.error('Wrong password')
return
try:
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM):
STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE):
STATUS[MY_PAGES.DEVICE_CLIMATE][overview.id] = overview
for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG):
STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview
except ConnectionError as ex:
for overview in get_function():
status[overview.id] = overview
except (ConnectionError, VERISURE_ERROR) as ex:
_LOGGER.error('Caught connection error %s, tries to reconnect', ex)
reconnect()

View File

@ -16,7 +16,7 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
DOMAIN = "wink"
REQUIREMENTS = ['python-wink==0.3.1']
REQUIREMENTS = ['python-wink==0.4.1']
DISCOVER_LIGHTS = "wink.lights"
DISCOVER_SWITCHES = "wink.switches"
@ -37,9 +37,10 @@ def setup(hass, config):
# Load components for the devices in the Wink that we support
for component_name, func_exists, discovery_type in (
('light', pywink.get_bulbs, DISCOVER_LIGHTS),
('switch', pywink.get_switches, DISCOVER_SWITCHES),
('sensor', lambda: pywink.get_sensors or pywink.get_eggtrays,
DISCOVER_SENSORS),
('switch', lambda: pywink.get_switches or
pywink.get_powerstrip_outlets, DISCOVER_SWITCHES),
('sensor', lambda: pywink.get_sensors or
pywink.get_eggtrays, DISCOVER_SENSORS),
('lock', pywink.get_locks, DISCOVER_LOCKS)):
if func_exists():

View File

@ -26,6 +26,9 @@ CONF_POLLING_INTERVAL = "polling_interval"
DEFAULT_ZWAVE_CONFIG_PATH = os.path.join(sys.prefix, 'share',
'python-openzwave', 'config')
SERVICE_ADD_NODE = "add_node"
SERVICE_REMOVE_NODE = "remove_node"
DISCOVER_SENSORS = "zwave.sensors"
DISCOVER_SWITCHES = "zwave.switch"
DISCOVER_LIGHTS = "zwave.light"
@ -37,6 +40,7 @@ COMMAND_CLASS_SENSOR_BINARY = 48
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
COMMAND_CLASS_METER = 50
COMMAND_CLASS_BATTERY = 128
COMMAND_CLASS_ALARM = 113 # 0x71
GENRE_WHATEVER = None
GENRE_USER = "User"
@ -53,7 +57,8 @@ DISCOVERY_COMPONENTS = [
DISCOVER_SENSORS,
[COMMAND_CLASS_SENSOR_BINARY,
COMMAND_CLASS_SENSOR_MULTILEVEL,
COMMAND_CLASS_METER],
COMMAND_CLASS_METER,
COMMAND_CLASS_ALARM],
TYPE_WHATEVER,
GENRE_USER),
('light',
@ -176,6 +181,14 @@ def setup(hass, config):
dispatcher.connect(
value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
def add_node(event):
""" Switch into inclusion mode """
NETWORK.controller.begin_command_add_device()
def remove_node(event):
""" Switch into exclusion mode"""
NETWORK.controller.begin_command_remove_device()
def stop_zwave(event):
""" Stop Z-wave. """
NETWORK.stop()
@ -190,6 +203,11 @@ def setup(hass, config):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
# register add / remove node services for zwave sticks without
# hardware inclusion button
hass.services.register(DOMAIN, SERVICE_ADD_NODE, add_node)
hass.services.register(DOMAIN, SERVICE_REMOVE_NODE, remove_node)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
return True

View File

@ -1,7 +1,7 @@
# coding: utf-8
""" Constants used by Home Assistant components. """
__version__ = "0.10.1"
__version__ = "0.11.0"
# Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*'
@ -24,6 +24,7 @@ CONF_USERNAME = "username"
CONF_PASSWORD = "password"
CONF_API_KEY = "api_key"
CONF_ACCESS_TOKEN = "access_token"
CONF_FILENAME = "filename"
CONF_VALUE_TEMPLATE = "value_template"

View File

@ -1,6 +1,5 @@
"""
homeassistant
~~~~~~~~~~~~~
Core components of Home Assistant.
Home Assistant is a Home Automation framework for observing the state
of entities and react to changes.
@ -53,9 +52,10 @@ _MockHA = namedtuple("MockHomeAssistant", ['bus'])
class HomeAssistant(object):
""" Core class to route all communication to right components. """
"""Root object of the Home Assistant home automation."""
def __init__(self):
"""Initialize new Home Assistant object."""
self.pool = pool = create_worker_pool()
self.bus = EventBus(pool)
self.services = ServiceRegistry(self.bus, pool)
@ -63,7 +63,7 @@ class HomeAssistant(object):
self.config = Config()
def start(self):
""" Start home assistant. """
"""Start home assistant."""
_LOGGER.info(
"Starting Home Assistant (%d threads)", self.pool.worker_count)
@ -71,12 +71,11 @@ class HomeAssistant(object):
self.bus.fire(EVENT_HOMEASSISTANT_START)
def block_till_stopped(self):
""" Will register service homeassistant/stop and
will block until called. """
"""Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event()
def stop_homeassistant(*args):
""" Stops Home Assistant. """
"""Stop Home Assistant."""
request_shutdown.set()
self.services.register(
@ -98,7 +97,7 @@ class HomeAssistant(object):
self.stop()
def stop(self):
""" Stops Home Assistant and shuts down all threads. """
"""Stop Home Assistant and shuts down all threads."""
_LOGGER.info("Stopping")
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
@ -150,8 +149,7 @@ class HomeAssistant(object):
class JobPriority(util.OrderedEnum):
""" Provides priorities for bus events. """
# pylint: disable=no-init,too-few-public-methods
"""Provides job priorities for event bus jobs."""
EVENT_CALLBACK = 0
EVENT_SERVICE = 1
@ -161,7 +159,7 @@ class JobPriority(util.OrderedEnum):
@staticmethod
def from_event_type(event_type):
""" Returns a priority based on event type. """
"""Return a priority based on event type."""
if event_type == EVENT_TIME_CHANGED:
return JobPriority.EVENT_TIME
elif event_type == EVENT_STATE_CHANGED:
@ -175,8 +173,7 @@ class JobPriority(util.OrderedEnum):
class EventOrigin(enum.Enum):
""" Distinguish between origin of event. """
# pylint: disable=no-init,too-few-public-methods
"""Represents origin of an event."""
local = "LOCAL"
remote = "REMOTE"
@ -185,14 +182,15 @@ class EventOrigin(enum.Enum):
return self.value
# pylint: disable=too-few-public-methods
class Event(object):
""" Represents an event within the Bus. """
# pylint: disable=too-few-public-methods
"""Represents an event within the Bus."""
__slots__ = ['event_type', 'data', 'origin', 'time_fired']
def __init__(self, event_type, data=None, origin=EventOrigin.local,
time_fired=None):
"""Initialize a new event."""
self.event_type = event_type
self.data = data or {}
self.origin = origin
@ -200,7 +198,7 @@ class Event(object):
time_fired or dt_util.utcnow())
def as_dict(self):
""" Returns a dict representation of this Event. """
"""Create a dict representation of this Event."""
return {
'event_type': self.event_type,
'data': dict(self.data),
@ -227,26 +225,23 @@ class Event(object):
class EventBus(object):
""" Class that allows different components to communicate via services
and events.
"""
"""Allows firing of and listening for events."""
def __init__(self, pool=None):
"""Initialize a new event bus."""
self._listeners = {}
self._lock = threading.Lock()
self._pool = pool or create_worker_pool()
@property
def listeners(self):
""" Dict with events that is being listened for and the number
of listeners.
"""
"""Dict with events and the number of listeners."""
with self._lock:
return {key: len(self._listeners[key])
for key in self._listeners}
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
""" Fire an event. """
"""Fire an event."""
if not self._pool.running:
raise HomeAssistantError('Home Assistant has shut down.')
@ -271,7 +266,7 @@ class EventBus(object):
self._pool.add_job(job_priority, (func, event))
def listen(self, event_type, listener):
""" Listen for all events or events of a specific type.
"""Listen for all events or events of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
as event_type.
@ -283,7 +278,7 @@ class EventBus(object):
self._listeners[event_type] = [listener]
def listen_once(self, event_type, listener):
""" Listen once for event of a specific type.
"""Listen once for event of a specific type.
To listen to all events specify the constant ``MATCH_ALL``
as event_type.
@ -292,7 +287,7 @@ class EventBus(object):
"""
@ft.wraps(listener)
def onetime_listener(event):
""" Removes listener from eventbus and then fires listener. """
"""Remove listener from eventbus and then fires listener."""
if hasattr(onetime_listener, 'run'):
return
# Set variable so that we will never run twice.
@ -311,7 +306,7 @@ class EventBus(object):
return onetime_listener
def remove_listener(self, event_type, listener):
""" Removes a listener of a specific event_type. """
"""Remove a listener of a specific event_type."""
with self._lock:
try:
self._listeners[event_type].remove(listener)
@ -343,6 +338,7 @@ class State(object):
# pylint: disable=too-many-arguments
def __init__(self, entity_id, state, attributes=None, last_changed=None,
last_updated=None):
"""Initialize a new state."""
if not ENTITY_ID_PATTERN.match(entity_id):
raise InvalidEntityFormatError((
"Invalid entity id encountered: {}. "
@ -363,31 +359,33 @@ class State(object):
@property
def domain(self):
""" Returns domain of this state. """
"""Domain of this state."""
return util.split_entity_id(self.entity_id)[0]
@property
def object_id(self):
""" Returns object_id of this state. """
"""Object id of this state."""
return util.split_entity_id(self.entity_id)[1]
@property
def name(self):
""" Name to represent this state. """
"""Name of this state."""
return (
self.attributes.get(ATTR_FRIENDLY_NAME) or
self.object_id.replace('_', ' '))
def copy(self):
""" Creates a copy of itself. """
"""Return a copy of the state."""
return State(self.entity_id, self.state,
dict(self.attributes), self.last_changed,
self.last_updated)
def as_dict(self):
""" Converts State to a dict to be used within JSON.
Ensures: state == State.from_dict(state.as_dict()) """
"""Return a dict representation of the State.
To be used for JSON serialization.
Ensures: state == State.from_dict(state.as_dict())
"""
return {'entity_id': self.entity_id,
'state': self.state,
'attributes': self.attributes,
@ -396,11 +394,11 @@ class State(object):
@classmethod
def from_dict(cls, json_dict):
""" Static method to create a state from a dict.
Ensures: state == State.from_json_dict(state.to_json_dict()) """
"""Initialize a state from a dict.
if not (json_dict and
'entity_id' in json_dict and
Ensures: state == State.from_json_dict(state.to_json_dict())
"""
if not (json_dict and 'entity_id' in json_dict and
'state' in json_dict):
return None
@ -433,15 +431,16 @@ class State(object):
class StateMachine(object):
""" Helper class that tracks the state of different entities. """
"""Helper class that tracks the state of different entities."""
def __init__(self, bus):
"""Initialize state machine."""
self._states = {}
self._bus = bus
self._lock = threading.Lock()
def entity_ids(self, domain_filter=None):
""" List of entity ids that are being tracked. """
"""List of entity ids that are being tracked."""
if domain_filter is None:
return list(self._states.keys())
@ -451,35 +450,43 @@ class StateMachine(object):
if state.domain == domain_filter]
def all(self):
""" Returns a list of all states. """
"""Create a list of all states."""
with self._lock:
return [state.copy() for state in self._states.values()]
def get(self, entity_id):
""" Returns the state of the specified entity. """
"""Retrieve state of entity_id or None if not found."""
state = self._states.get(entity_id.lower())
# Make a copy so people won't mutate the state
return state.copy() if state else None
def is_state(self, entity_id, state):
""" Returns True if entity exists and is specified state. """
"""Test if entity exists and is specified state."""
entity_id = entity_id.lower()
return (entity_id in self._states and
self._states[entity_id].state == state)
def remove(self, entity_id):
""" Removes an entity from the state machine.
def is_state_attr(self, entity_id, name, value):
"""Test if entity exists and has a state attribute set to value."""
entity_id = entity_id.lower()
Returns boolean to indicate if an entity was removed. """
return (entity_id in self._states and
self._states[entity_id].attributes.get(name, None) == value)
def remove(self, entity_id):
"""Remove the state of an entity.
Returns boolean to indicate if an entity was removed.
"""
entity_id = entity_id.lower()
with self._lock:
return self._states.pop(entity_id, None) is not None
def set(self, entity_id, new_state, attributes=None):
""" Set the state of an entity, add entity if it does not exist.
"""Set the state of an entity, add entity if it does not exist.
Attributes is an optional dict to specify attributes of this state.
@ -514,9 +521,7 @@ class StateMachine(object):
self._bus.fire(EVENT_STATE_CHANGED, event_data)
def track_change(self, entity_ids, action, from_state=None, to_state=None):
"""
DEPRECATED AS OF 8/4/2015
"""
"""DEPRECATED AS OF 8/4/2015."""
_LOGGER.warning(
'hass.states.track_change is deprecated. '
'Use homeassistant.helpers.event.track_state_change instead.')
@ -527,33 +532,36 @@ class StateMachine(object):
# pylint: disable=too-few-public-methods
class Service(object):
""" Represents a service. """
"""Represents a callable service."""
__slots__ = ['func', 'description', 'fields']
def __init__(self, func, description, fields):
"""Initialize a service."""
self.func = func
self.description = description or ''
self.fields = fields or {}
def as_dict(self):
""" Return dictionary representation of this service. """
"""Return dictionary representation of this service."""
return {
'description': self.description,
'fields': self.fields,
}
def __call__(self, call):
"""Execute the service."""
self.func(call)
# pylint: disable=too-few-public-methods
class ServiceCall(object):
""" Represents a call to a service. """
"""Represents a call to a service."""
__slots__ = ['domain', 'service', 'data']
def __init__(self, domain, service, data=None):
"""Initialize a service call."""
self.domain = domain
self.service = service
self.data = data or {}
@ -567,9 +575,10 @@ class ServiceCall(object):
class ServiceRegistry(object):
""" Offers services over the eventbus. """
"""Offers services over the eventbus."""
def __init__(self, bus, pool=None):
"""Initialize a service registry."""
self._services = {}
self._lock = threading.Lock()
self._pool = pool or create_worker_pool()
@ -579,14 +588,14 @@ class ServiceRegistry(object):
@property
def services(self):
""" Dict with per domain a list of available services. """
"""Dict with per domain a list of available services."""
with self._lock:
return {domain: {key: value.as_dict() for key, value
in self._services[domain].items()}
for domain in self._services}
def has_service(self, domain, service):
""" Returns True if specified service exists. """
"""Test if specified service exists."""
return service in self._services.get(domain, [])
def register(self, domain, service, service_func, description=None):
@ -611,7 +620,8 @@ class ServiceRegistry(object):
def call(self, domain, service, service_data=None, blocking=False):
"""
Calls specified service.
Call a service.
Specify blocking=True to wait till service is executed.
Waits a maximum of SERVICE_CALL_LIMIT.
@ -635,10 +645,7 @@ class ServiceRegistry(object):
executed_event = threading.Event()
def service_executed(call):
"""
Called when a service is executed.
Will set the event if matches our service call.
"""
"""Callback method that is called when service is executed."""
if call.data[ATTR_SERVICE_CALL_ID] == call_id:
executed_event.set()
@ -653,7 +660,7 @@ class ServiceRegistry(object):
return success
def _event_to_service_call(self, event):
""" Calls a service from an event. """
"""Callback for SERVICE_CALLED events from the event bus."""
service_data = dict(event.data)
domain = service_data.pop(ATTR_DOMAIN, None)
service = service_data.pop(ATTR_SERVICE, None)
@ -670,7 +677,7 @@ class ServiceRegistry(object):
(service_handler, service_call)))
def _execute_service(self, service_and_call):
""" Executes a service and fires a SERVICE_EXECUTED event. """
"""Execute a service and fires a SERVICE_EXECUTED event."""
service, call = service_and_call
service(call)
@ -680,16 +687,17 @@ class ServiceRegistry(object):
{ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]})
def _generate_unique_id(self):
""" Generates a unique service call id. """
"""Generate a unique service call id."""
self._cur_id += 1
return "{}-{}".format(id(self), self._cur_id)
class Config(object):
""" Configuration settings for Home Assistant. """
"""Configuration settings for Home Assistant."""
# pylint: disable=too-many-instance-attributes
def __init__(self):
"""Initialize a new config object."""
self.latitude = None
self.longitude = None
self.temperature_unit = None
@ -709,15 +717,15 @@ class Config(object):
self.config_dir = get_default_config_dir()
def distance(self, lat, lon):
""" Calculate distance from Home Assistant in meters. """
"""Calculate distance from Home Assistant in meters."""
return location.distance(self.latitude, self.longitude, lat, lon)
def path(self, *path):
""" Returns path to the file within the config dir. """
"""Generate path to the file within the config dir."""
return os.path.join(self.config_dir, *path)
def temperature(self, value, unit):
""" Converts temperature to user preferred unit if set. """
"""Convert temperature to user preferred unit if set."""
if not (unit in (TEMP_CELCIUS, TEMP_FAHRENHEIT) and
self.temperature_unit and unit != self.temperature_unit):
return value, unit
@ -732,7 +740,7 @@ class Config(object):
self.temperature_unit)
def as_dict(self):
""" Converts config to a dictionary. """
"""Create a dict representation of this dict."""
time_zone = self.time_zone or dt_util.UTC
return {
@ -747,7 +755,7 @@ class Config(object):
def create_timer(hass, interval=TIMER_INTERVAL):
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
"""Create a timer that will start on HOMEASSISTANT_START."""
# We want to be able to fire every time a minute starts (seconds=0).
# We want this so other modules can use that to make sure they fire
# every minute.
@ -810,12 +818,12 @@ def create_timer(hass, interval=TIMER_INTERVAL):
def create_worker_pool(worker_count=None):
""" Creates a worker pool to be used. """
"""Create a worker pool."""
if worker_count is None:
worker_count = MIN_WORKER_THREAD
def job_handler(job):
""" Called whenever a job is available to do. """
"""Called whenever a job is available to do."""
try:
func, arg = job
func(arg)
@ -825,8 +833,7 @@ def create_worker_pool(worker_count=None):
_LOGGER.exception("BusHandler:Exception doing job")
def busy_callback(worker_count, current_jobs, pending_jobs_count):
""" Callback to be called when the pool queue gets too big. """
"""Callback to be called when the pool queue gets too big."""
_LOGGER.warning(
"WorkerPool:All %d threads are busy and %d jobs pending",
worker_count, pending_jobs_count)

View File

@ -36,7 +36,7 @@ def extract_entity_ids(hass, service):
service_ent_id = service.data[ATTR_ENTITY_ID]
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)]

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 @@
"""
homeassistant.helpers.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Helpers that help with state related things.
"""
"""Helpers that help with state related things."""
from collections import defaultdict
import json
import logging
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
is exited.
"""
def __init__(self, hass):
"""Initialize a TrackStates block."""
self.hass = hass
self.states = []
def __enter__(self):
"""Record time from which to track changes."""
self.now = dt_util.utcnow()
return self.states
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))
def get_changed_since(states, utc_point_in_time):
"""
Returns all states that have been changed since utc_point_in_time.
"""
"""List of states that have been changed since 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]
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):
states = [states]
to_call = defaultdict(list)
for state in states:
current_state = hass.states.get(state.entity_id)
@ -76,7 +77,18 @@ def reproduce_state(hass, states, blocking=False):
state)
continue
service_data = dict(state.attributes)
service_data[ATTR_ENTITY_ID] = state.entity_id
if state.domain == 'group':
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

@ -53,8 +53,12 @@ def color_xy_brightness_to_RGB(vX, vY, brightness):
return (0, 0, 0)
Y = brightness
X = (Y / vY) * vX
Z = (Y / vY) * (1 - vX - vY)
if vY != 0:
X = (Y / vY) * vX
Z = (Y / vY) * (1 - vX - vY)
else:
X = 0
Z = 0
# Convert to RGB using Wide RGB D65 conversion.
r = X * 1.612 - Y * 0.203 - Z * 0.302

View File

@ -108,14 +108,14 @@ def datetime_to_date_str(dattim):
return dattim.strftime(DATE_STR_FORMAT)
def str_to_datetime(dt_str):
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
""" Converts a string to a UTC datetime object.
@rtype: datetime
"""
try:
return dt.datetime.strptime(
dt_str, DATETIME_STR_FORMAT).replace(tzinfo=pytz.utc)
dt_str, dt_format).replace(tzinfo=pytz.utc)
except ValueError: # If dt_str did not match our format
return None

View File

@ -4,6 +4,8 @@ import collections
import requests
from vincenty import vincenty
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
LocationInfo = collections.namedtuple(
"LocationInfo",
@ -34,3 +36,20 @@ def detect_location_info():
def distance(lat1, lon1, lat2, lon2):
""" Calculate the distance in meters between two points. """
return vincenty((lat1, lon1), (lat2, lon2)) * 1000
def elevation(latitude, longitude):
""" Return elevation for given latitude and longitude. """
req = requests.get(ELEVATION_URL, params={
'locations': '{},{}'.format(latitude, longitude),
'sensor': 'false',
})
if req.status_code != 200:
return 0
try:
return int(float(req.json()['results'][0]['elevation']))
except (ValueError, KeyError):
return 0

View File

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

View File

@ -1,2 +0,0 @@
[pytest]
testpaths = tests

View File

@ -1,5 +0,0 @@
requests>=2,<3
pyyaml>=3.11,<4
pytz>=2015.4
pip>=7.0.0
vincenty==0.1.3

Some files were not shown because too many files have changed in this diff Show More