Merge branch 'dev' of https://github.com/balloob/home-assistant into findiphone

This commit is contained in:
Daren Lord 2015-12-04 09:05:23 -07:00
commit b6342ed848
148 changed files with 3793 additions and 1317 deletions

View File

@ -17,6 +17,9 @@ omit =
homeassistant/components/*/tellstick.py homeassistant/components/*/tellstick.py
homeassistant/components/*/vera.py homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/verisure.py homeassistant/components/verisure.py
homeassistant/components/*/verisure.py homeassistant/components/*/verisure.py
@ -29,7 +32,7 @@ omit =
homeassistant/components/rfxtrx.py homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py homeassistant/components/*/rfxtrx.py
homeassistant/components/ifttt.py homeassistant/components/binary_sensor/arest.py
homeassistant/components/browser.py homeassistant/components/browser.py
homeassistant/components/camera/* homeassistant/components/camera/*
homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/actiontec.py
@ -48,6 +51,7 @@ omit =
homeassistant/components/device_tracker/snmp.py homeassistant/components/device_tracker/snmp.py
homeassistant/components/discovery.py homeassistant/components/discovery.py
homeassistant/components/downloader.py homeassistant/components/downloader.py
homeassistant/components/ifttt.py
homeassistant/components/keyboard.py homeassistant/components/keyboard.py
homeassistant/components/light/hue.py homeassistant/components/light/hue.py
homeassistant/components/light/mqtt.py homeassistant/components/light/mqtt.py
@ -84,7 +88,6 @@ omit =
homeassistant/components/sensor/glances.py homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/openweathermap.py
homeassistant/components/switch/orvibo.py
homeassistant/components/sensor/rest.py homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/sabnzbd.py
@ -98,10 +101,13 @@ omit =
homeassistant/components/switch/command_switch.py homeassistant/components/switch/command_switch.py
homeassistant/components/switch/edimax.py homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/rest.py homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_gpio.py homeassistant/components/switch/rpi_gpio.py
homeassistant/components/switch/transmission.py homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/homematic.py
homeassistant/components/thermostat/honeywell.py homeassistant/components/thermostat/honeywell.py
homeassistant/components/thermostat/nest.py homeassistant/components/thermostat/nest.py
homeassistant/components/thermostat/radiotherm.py homeassistant/components/thermostat/radiotherm.py

View File

@ -2,9 +2,10 @@ sudo: false
language: python language: python
cache: cache:
directories: directories:
- $HOME/virtualenv/python3.4.2/ - $HOME/virtualenv/python$TRAVIS_PYTHON_VERSION/
python: python:
- "3.4" - 3.4.2
- 3.5.0
install: install:
- script/bootstrap_server - script/bootstrap_server
script: script:

View File

@ -1,19 +1,27 @@
FROM python:3-onbuild FROM python:3.4
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
VOLUME /config VOLUME /config
RUN pip3 install --no-cache-dir -r requirements_all.txt RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# For the nmap tracker # For the nmap tracker
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \ apt-get install -y --no-install-recommends nmap net-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y cython3 libudev-dev && \ apt-get install -y cython3 libudev-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip3 install "cython<0.23" && \ pip3 install "cython<0.23" && \
script/build_python_openzwave script/build_python_openzwave
COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt
# Copy source
COPY . .
CMD [ "python", "-m", "homeassistant", "--config", "/config" ] CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@ -82,7 +82,7 @@ def _setup_component(hass, domain, config):
return True return True
component = loader.get_component(domain) component = loader.get_component(domain)
missing_deps = [dep for dep in component.DEPENDENCIES missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components] if dep not in hass.config.components]
if missing_deps: if missing_deps:
@ -106,7 +106,7 @@ def _setup_component(hass, domain, config):
# Assumption: if a component does not depend on groups # Assumption: if a component does not depend on groups
# it communicates with devices # it communicates with devices
if group.DOMAIN not in component.DEPENDENCIES: if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker() hass.pool.add_worker()
hass.bus.fire( hass.bus.fire(
@ -133,8 +133,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform return platform
# Load dependencies # Load dependencies
if hasattr(platform, 'DEPENDENCIES'): for component in getattr(platform, 'DEPENDENCIES', []):
for component in platform.DEPENDENCIES:
if not setup_component(hass, component, config): if not setup_component(hass, component, config):
_LOGGER.error( _LOGGER.error(
'Unable to prepare setup for platform %s because ' 'Unable to prepare setup for platform %s because '

View File

@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel' DOMAIN = 'alarm_control_panel'
DEPENDENCIES = []
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'

View File

@ -0,0 +1,13 @@
"""
homeassistant.components.alarm_control_panel.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake alarm control panels.
"""
import homeassistant.components.alarm_control_panel.manual as manual
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Demo alarm control panels. """
add_devices([
manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10),
])

View File

@ -18,8 +18,6 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
DEFAULT_ALARM_NAME = 'HA Alarm' DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60 DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120 DEFAULT_TRIGGER_TIME = 120

View File

@ -18,10 +18,10 @@ from homeassistant.bootstrap import ERROR_LOG_FILENAME
from homeassistant.const import ( from homeassistant.const import (
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM, URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS, URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG, URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG, URL_API_LOG_OUT,
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
HTTP_UNPROCESSABLE_ENTITY, CONTENT_TYPE_TEXT_PLAIN) HTTP_UNPROCESSABLE_ENTITY)
DOMAIN = 'api' DOMAIN = 'api'
@ -36,10 +36,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" Register the API with the HTTP interface. """ """ Register the API with the HTTP interface. """
if 'http' not in hass.config.components:
_LOGGER.error('Dependency http is not loaded')
return False
# /api - for validation purposes # /api - for validation purposes
hass.http.register_path('GET', URL_API, _handle_get_api) hass.http.register_path('GET', URL_API, _handle_get_api)
@ -93,6 +89,8 @@ def setup(hass, config):
hass.http.register_path('GET', URL_API_ERROR_LOG, hass.http.register_path('GET', URL_API_ERROR_LOG,
_handle_get_api_error_log) _handle_get_api_error_log)
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
return True return True
@ -108,6 +106,7 @@ def _handle_get_api_stream(handler, path_match, data):
wfile = handler.wfile wfile = handler.wfile
write_lock = threading.Lock() write_lock = threading.Lock()
block = threading.Event() block = threading.Event()
session_id = None
restrict = data.get('restrict') restrict = data.get('restrict')
if restrict: if restrict:
@ -121,6 +120,7 @@ def _handle_get_api_stream(handler, path_match, data):
try: try:
wfile.write(msg.encode("UTF-8")) wfile.write(msg.encode("UTF-8"))
wfile.flush() wfile.flush()
handler.server.sessions.extend_validation(session_id)
except IOError: except IOError:
block.set() block.set()
@ -140,6 +140,7 @@ def _handle_get_api_stream(handler, path_match, data):
handler.send_response(HTTP_OK) handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/event-stream') handler.send_header('Content-type', 'text/event-stream')
session_id = handler.set_session_cookie_header()
handler.end_headers() handler.end_headers()
hass.bus.listen(MATCH_ALL, forward_events) hass.bus.listen(MATCH_ALL, forward_events)
@ -347,9 +348,15 @@ def _handle_get_api_components(handler, path_match, data):
def _handle_get_api_error_log(handler, path_match, data): def _handle_get_api_error_log(handler, path_match, data):
""" Returns the logged errors for this session. """ """ Returns the logged errors for this session. """
error_path = handler.server.hass.config.path(ERROR_LOG_FILENAME) handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
with open(error_path, 'rb') as error_log: False)
handler.write_file_pointer(CONTENT_TYPE_TEXT_PLAIN, error_log)
def _handle_post_api_log_out(handler, path_match, data):
""" Log user out. """
handler.send_response(HTTP_OK)
handler.destroy_session()
handler.end_headers()
def _services_json(hass): def _services_json(hass):

View File

@ -19,7 +19,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
DOMAIN = "arduino" DOMAIN = "arduino"
DEPENDENCIES = []
REQUIREMENTS = ['PyMata==2.07a'] REQUIREMENTS = ['PyMata==2.07a']
BOARD = None BOARD = None
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -123,7 +123,7 @@ def _migrate_old_config(config):
_LOGGER.warning( _LOGGER.warning(
'You are using an old configuration format. Please upgrade: ' 'You are using an old configuration format. Please upgrade: '
'https://home-assistant.io/components/automation.html') 'https://home-assistant.io/components/automation/')
new_conf = { new_conf = {
CONF_TRIGGER: dict(config), CONF_TRIGGER: dict(config),

View File

@ -14,6 +14,7 @@ from homeassistant.helpers.event import track_state_change
CONF_ENTITY_ID = "entity_id" CONF_ENTITY_ID = "entity_id"
CONF_BELOW = "below" CONF_BELOW = "below"
CONF_ABOVE = "above" CONF_ABOVE = "above"
CONF_ATTRIBUTE = "attribute"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,6 +29,7 @@ def trigger(hass, config, action):
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE) above = config.get(CONF_ABOVE)
attribute = config.get(CONF_ATTRIBUTE)
if below is None and above is None: if below is None and above is None:
_LOGGER.error("Missing configuration key." _LOGGER.error("Missing configuration key."
@ -40,8 +42,8 @@ def trigger(hass, config, action):
""" Listens for state changes and calls action. """ """ Listens for state changes and calls action. """
# Fire action if we go from outside range into range # Fire action if we go from outside range into range
if _in_range(to_s.state, above, below) and \ if _in_range(to_s, above, below, attribute) and \
(from_s is None or not _in_range(from_s.state, above, below)): (from_s is None or not _in_range(from_s, above, below, attribute)):
action() action()
track_state_change( track_state_change(
@ -61,6 +63,7 @@ def if_action(hass, config):
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE) above = config.get(CONF_ABOVE)
attribute = config.get(CONF_ATTRIBUTE)
if below is None and above is None: if below is None and above is None:
_LOGGER.error("Missing configuration key." _LOGGER.error("Missing configuration key."
@ -71,18 +74,19 @@ def if_action(hass, config):
def if_numeric_state(): def if_numeric_state():
""" Test numeric state condition. """ """ Test numeric state condition. """
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
return state is not None and _in_range(state.state, above, below) return state is not None and _in_range(state, above, below, attribute)
return if_numeric_state return if_numeric_state
def _in_range(value, range_start, range_end): def _in_range(state, range_start, range_end, attribute):
""" Checks if value is inside the range """ """ Checks if value is inside the range """
value = (state.state if attribute is None
else state.attributes.get(attribute))
try: try:
value = float(value) value = float(value)
except ValueError: except ValueError:
_LOGGER.warn("Missing value in numeric check") _LOGGER.warning("Missing value in numeric check")
return False return False
if range_start is not None and range_end is not None: if range_start is not None and range_end is not None:

View File

@ -46,6 +46,7 @@ def trigger(hass, config, action):
from_match = _in_zone(hass, zone_entity_id, from_s) if from_s else None from_match = _in_zone(hass, zone_entity_id, from_s) if from_s else None
to_match = _in_zone(hass, zone_entity_id, to_s) to_match = _in_zone(hass, zone_entity_id, to_s)
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \ if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match: event == EVENT_LEAVE and from_match and not to_match:
action() action()

View File

@ -14,7 +14,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.const import (STATE_ON, STATE_OFF)
DOMAIN = 'binary_sensor' DOMAIN = 'binary_sensor'
DEPENDENCIES = []
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'

View File

@ -0,0 +1,107 @@
"""
homeassistant.components.binary_sensor.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest sensor will consume an exposed aREST API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/
"""
from datetime import timedelta
import logging
import requests
from homeassistant.util import Throttle
from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
CONF_RESOURCE = 'resource'
CONF_PIN = 'pin'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the aREST binary sensor. """
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
if None in (resource, pin):
_LOGGER.error('Not all required config keys present: %s',
', '.join((CONF_RESOURCE, CONF_PIN)))
return False
try:
response = requests.get(resource, timeout=10).json()
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 device at %s. '
'Please check the IP address in the configuration file.',
resource)
return False
arest = ArestData(resource, pin)
add_devices([ArestBinarySensor(arest,
resource,
config.get('name', response['name']),
pin)])
# pylint: disable=too-many-instance-attributes, too-many-arguments
class ArestBinarySensor(BinarySensorDevice):
""" Implements an aREST binary sensor for a pin. """
def __init__(self, arest, resource, name, pin):
self.arest = arest
self._resource = resource
self._name = name
self._pin = pin
self.update()
if self._pin is not None:
request = requests.get('{}/mode/{}/i'.format
(self._resource, self._pin), timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
@property
def name(self):
""" The name of the binary sensor. """
return self._name
@property
def is_on(self):
""" True if the binary sensor is on. """
return bool(self.arest.data.get('state'))
def update(self):
""" Gets the latest data from aREST API. """
self.arest.update()
# pylint: disable=too-few-public-methods
class ArestData(object):
""" Class for handling the data retrieval for pins. """
def __init__(self, resource, pin):
self._resource = resource
self._pin = pin
self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from aREST device. """
try:
response = requests.get('{}/digital/{}'.format(
self._resource, self._pin), timeout=10)
self.data = {'state': response.json()['return_value']}
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device '%s'. Is device offline?",
self._resource)

View File

@ -9,18 +9,17 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Demo binary sensors. """ """ Sets up the Demo binary sensors. """
add_devices([ add_devices([
DemoBinarySensor('Window Bathroom', True, None), DemoBinarySensor('Basement Floor Wet', False),
DemoBinarySensor('Floor Basement', False, None), DemoBinarySensor('Movement Backyard', True),
]) ])
class DemoBinarySensor(BinarySensorDevice): class DemoBinarySensor(BinarySensorDevice):
""" A Demo binary sensor. """ """ A Demo binary sensor. """
def __init__(self, name, state, icon=None): def __init__(self, name, state):
self._name = name self._name = name
self._state = state self._state = state
self._icon = icon
@property @property
def should_poll(self): def should_poll(self):
@ -32,11 +31,6 @@ class DemoBinarySensor(BinarySensorDevice):
""" Returns the name of the binary sensor. """ """ Returns the name of the binary sensor. """
return self._name return self._name
@property
def icon(self):
""" Returns the icon to use for device if any. """
return self._icon
@property @property
def is_on(self): def is_on(self):
""" True if the binary sensor is on. """ """ True if the binary sensor is on. """

View File

@ -0,0 +1,76 @@
"""
homeassistant.components.binary_sensor.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a MQTT binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.components.mqtt as mqtt
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEPENDENCIES = ['mqtt']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Add MQTT binary sensor. """
if config.get('state_topic') is None:
_LOGGER.error('Missing required variable: state_topic')
return False
add_devices([MqttBinarySensor(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic', None),
config.get('qos', DEFAULT_QOS),
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttBinarySensor(BinarySensorDevice):
""" Represents a binary sensor that is updated by MQTT. """
def __init__(self, hass, name, state_topic, qos, payload_on, payload_off):
self._hass = hass
self._name = name
self._state = False
self._state_topic = state_topic
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
if payload == self._payload_on:
self._state = True
self.update_ha_state()
elif payload == self._payload_off:
self._state = False
self.update_ha_state()
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" The name of the binary sensor. """
return self._name
@property
def is_on(self):
""" True if the binary sensor is on. """
return self._state

View File

@ -8,7 +8,6 @@ https://home-assistant.io/components/browser/
""" """
DOMAIN = "browser" DOMAIN = "browser"
DEPENDENCIES = []
SERVICE_BROWSE_URL = "browse_url" SERVICE_BROWSE_URL = "browse_url"

View File

@ -7,19 +7,20 @@ Component to interface with various cameras.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/ https://home-assistant.io/components/camera/
""" """
import requests
import logging import logging
import time
import re import re
import time
import requests
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_PICTURE, ATTR_ENTITY_PICTURE,
HTTP_NOT_FOUND, HTTP_NOT_FOUND,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
) )
from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'camera' DOMAIN = 'camera'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
@ -80,19 +81,21 @@ def setup(hass, config):
def _proxy_camera_image(handler, path_match, data): def _proxy_camera_image(handler, path_match, data):
""" Proxies the camera image via the HA server. """ """ Proxies the camera image via the HA server. """
entity_id = path_match.group(ATTR_ENTITY_ID) entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
camera = None if camera is None:
if entity_id in component.entities.keys(): handler.send_response(HTTP_NOT_FOUND)
camera = component.entities[entity_id] handler.end_headers()
return
if camera:
response = camera.camera_image() response = camera.camera_image()
if response is not None:
if response is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
handler.wfile.write(response) handler.wfile.write(response)
else:
handler.send_response(HTTP_NOT_FOUND)
else:
handler.send_response(HTTP_NOT_FOUND)
hass.http.register_path( hass.http.register_path(
'GET', 'GET',
@ -108,12 +111,9 @@ def setup(hass, config):
stream even with only a still image URL available. stream even with only a still image URL available.
""" """
entity_id = path_match.group(ATTR_ENTITY_ID) entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
camera = None if camera is None:
if entity_id in component.entities.keys():
camera = component.entities[entity_id]
if not camera:
handler.send_response(HTTP_NOT_FOUND) handler.send_response(HTTP_NOT_FOUND)
handler.end_headers() handler.end_headers()
return return
@ -131,7 +131,6 @@ def setup(hass, config):
# MJPEG_START_HEADER.format() # MJPEG_START_HEADER.format()
while True: while True:
img_bytes = camera.camera_image() img_bytes = camera.camera_image()
if img_bytes is None: if img_bytes is None:
continue continue
@ -148,12 +147,12 @@ def setup(hass, config):
handler.request.sendall( handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8')) bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
except (requests.RequestException, IOError): except (requests.RequestException, IOError):
camera.is_streaming = False camera.is_streaming = False
camera.update_ha_state() camera.update_ha_state()
camera.is_streaming = False
hass.http.register_path( hass.http.register_path(
'GET', 'GET',
re.compile( re.compile(

View File

@ -4,8 +4,8 @@ homeassistant.components.camera.demo
Demo platform that has a fake camera. Demo platform that has a fake camera.
""" """
import os import os
from random import randint
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
import homeassistant.util.dt as dt_util
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -24,12 +24,12 @@ class DemoCamera(Camera):
def camera_image(self): def camera_image(self):
""" Return a faked still image response. """ """ Return a faked still image response. """
now = dt_util.utcnow()
image_path = os.path.join(os.path.dirname(__file__), image_path = os.path.join(os.path.dirname(__file__),
'demo_{}.png'.format(randint(1, 5))) 'demo_{}.jpg'.format(now.second % 4))
with open(image_path, 'rb') as file: with open(image_path, 'rb') as file:
output = file.read() return file.read()
return output
@property @property
def name(self): def name(self):

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -7,11 +7,12 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.foscam/ https://home-assistant.io/components/camera.foscam/
""" """
import logging import logging
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN
from homeassistant.components.camera import Camera
import requests import requests
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -7,11 +7,12 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.generic/ https://home-assistant.io/components/camera.generic/
""" """
import logging import logging
from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN
from homeassistant.components.camera import Camera
import requests import requests
from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -6,13 +6,14 @@ Support for IP Cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.mjpeg/ https://home-assistant.io/components/camera.mjpeg/
""" """
import logging
from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN
from homeassistant.components.camera import Camera
import requests
from contextlib import closing from contextlib import closing
import logging
import requests
from requests.auth import HTTPBasicAuth
from homeassistant.helpers import validate_config
from homeassistant.components.camera import DOMAIN, Camera
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -15,7 +15,6 @@ from homeassistant.helpers import generate_entity_id
from homeassistant.const import EVENT_TIME_CHANGED from homeassistant.const import EVENT_TIME_CHANGED
DOMAIN = "configurator" DOMAIN = "configurator"
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"
SERVICE_CONFIGURE = "configure" SERVICE_CONFIGURE = "configure"

View File

@ -14,7 +14,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = "conversation" DOMAIN = "conversation"
DEPENDENCIES = []
SERVICE_PROCESS = "process" SERVICE_PROCESS = "process"

View File

@ -18,7 +18,7 @@ DEPENDENCIES = ['conversation', 'introduction', 'zone']
COMPONENTS_WITH_DEMO_PLATFORM = [ COMPONENTS_WITH_DEMO_PLATFORM = [
'device_tracker', 'light', 'media_player', 'notify', 'switch', 'sensor', 'device_tracker', 'light', 'media_player', 'notify', 'switch', 'sensor',
'thermostat', 'camera', 'binary_sensor'] 'thermostat', 'camera', 'binary_sensor', 'alarm_control_panel', 'lock']
def setup(hass, config): def setup(hass, config):
@ -55,23 +55,6 @@ def setup(hass, config):
group.setup_group(hass, 'bedroom', [lights[0], switches[1], group.setup_group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]]) media_players[0]])
# Setup IP Camera
bootstrap.setup_component(
hass, 'camera',
{'camera': {
'platform': 'generic',
'name': 'IP Camera',
'still_image_url': 'http://home-assistant.io/demo/webcam.jpg',
}})
# Setup alarm_control_panel
bootstrap.setup_component(
hass, 'alarm_control_panel',
{'alarm_control_panel': {
'platform': 'manual',
'name': 'Test Alarm',
}})
# Setup scripts # Setup scripts
bootstrap.setup_component( bootstrap.setup_component(
hass, 'script', hass, 'script',

View File

@ -98,7 +98,7 @@ class NmapDeviceScanner(object):
from nmap import PortScanner, PortScannerError from nmap import PortScanner, PortScannerError
scanner = PortScanner() scanner = PortScanner()
options = "-F --host-timeout 5" options = "-F --host-timeout 5s"
if self.home_interval: if self.home_interval:
boundary = dt_util.now() - self.home_interval boundary = dt_util.now() - self.home_interval

View File

@ -17,8 +17,7 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED) ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "discovery" DOMAIN = "discovery"
DEPENDENCIES = [] REQUIREMENTS = ['netdisco==0.5.2']
REQUIREMENTS = ['netdisco==0.5.1']
SCAN_INTERVAL = 300 # seconds SCAN_INTERVAL = 300 # seconds

View File

@ -15,7 +15,6 @@ from homeassistant.helpers import validate_config
from homeassistant.util import sanitize_filename from homeassistant.util import sanitize_filename
DOMAIN = "downloader" DOMAIN = "downloader"
DEPENDENCIES = []
SERVICE_DOWNLOAD_FILE = "download_file" SERVICE_DOWNLOAD_FILE = "download_file"

View File

@ -0,0 +1,155 @@
"""
homeassistant.components.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
"""
from datetime import timedelta
import logging
import os
from homeassistant.loader import get_component
from homeassistant import bootstrap
from homeassistant.util import Throttle
from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY)
DOMAIN = "ecobee"
DISCOVER_THERMOSTAT = "ecobee.thermostat"
DISCOVER_SENSORS = "ecobee.sensor"
NETWORK = None
HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'5645f843b64ac4f6e59dfb96233a07083c5e10c1.zip#python-ecobee==0.0.3']
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {}
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
def request_configuration(network, hass, config):
""" Request configuration steps from the user. """
configurator = get_component('configurator')
if 'ecobee' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['ecobee'], "Failed to register, please try again.")
return
# pylint: disable=unused-argument
def ecobee_configuration_callback(callback_data):
""" Actions to do when our configuration callback is called. """
network.request_tokens()
network.update()
setup_ecobee(hass, network, config)
_CONFIGURING['ecobee'] = configurator.request_config(
hass, "Ecobee", ecobee_configuration_callback,
description=(
'Please authorize this app at https://www.ecobee.com/consumer'
'portal/index.html with pin code: ' + network.pin),
description_image="/static/images/config_ecobee_thermostat.png",
submit_caption="I have authorized the app."
)
def setup_ecobee(hass, network, config):
""" Setup ecobee thermostat """
# If ecobee has a PIN then it needs to be configured.
if network.pin is not None:
request_configuration(network, hass, config)
return
if 'ecobee' in _CONFIGURING:
configurator = get_component('configurator')
configurator.request_done(_CONFIGURING.pop('ecobee'))
# Ensure component is loaded
bootstrap.setup_component(hass, 'thermostat', config)
bootstrap.setup_component(hass, 'sensor', config)
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
# Fire thermostat discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_THERMOSTAT,
ATTR_DISCOVERED: {'hold_temp': hold_temp}
})
# Fire sensor discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_SENSORS,
ATTR_DISCOVERED: {}
})
# pylint: disable=too-few-public-methods
class EcobeeData(object):
""" Gets the latest data and update the states. """
def __init__(self, config_file):
from pyecobee import Ecobee
self.ecobee = Ecobee(config_file)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Get the latest data from pyecobee. """
self.ecobee.update()
_LOGGER.info("ecobee data updated successfully.")
def setup(hass, config):
"""
Setup Ecobee.
Will automatically load thermostat and sensor components to support
devices discovered on the network.
"""
# pylint: disable=global-statement, import-error
global NETWORK
if 'ecobee' in _CONFIGURING:
return
from pyecobee import config_from_file
# Create ecobee.conf if it doesn't exist
if not os.path.isfile(hass.config.path(ECOBEE_CONFIG_FILE)):
if config[DOMAIN].get(CONF_API_KEY) is None:
_LOGGER.error("No ecobee api_key found in config.")
return
jsonconfig = {"API_KEY": config[DOMAIN].get(CONF_API_KEY)}
config_from_file(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)
NETWORK = EcobeeData(hass.config.path(ECOBEE_CONFIG_FILE))
setup_ecobee(hass, NETWORK.ecobee, config)
return True

View File

@ -54,8 +54,7 @@ def setup(hass, config):
def _handle_get_root(handler, path_match, data): def _handle_get_root(handler, path_match, data):
""" Renders the debug interface. """ """ Renders the frontend. """
handler.send_response(HTTP_OK) handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8') handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers() handler.end_headers()
@ -66,7 +65,7 @@ def _handle_get_root(handler, path_match, data):
app_url = "frontend-{}.html".format(version.VERSION) app_url = "frontend-{}.html".format(version.VERSION)
# auto login if no password was set, else check api_password param # auto login if no password was set, else check api_password param
auth = ('no_password_set' if handler.server.no_password_set auth = ('no_password_set' if handler.server.api_password is None
else data.get('api_password', '')) else data.get('api_password', ''))
with open(INDEX_PATH) as template_file: with open(INDEX_PATH) as template_file:

View File

@ -4,16 +4,13 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Home Assistant</title> <title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json' /> <link rel='manifest' href='/static/manifest.json'>
<link rel='shortcut icon' href='/static/favicon.ico' /> <link rel='icon' href='/static/favicon.ico'>
<link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='180x180' <link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'> href='/static/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'> <meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'> <meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width, <meta name='viewport' content='width=device-width, user-scalable=no'>
user-scalable=no' />
<meta name='theme-color' content='#03a9f4'> <meta name='theme-color' content='#03a9f4'>
<style> <style>
#init { #init {
@ -26,24 +23,17 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
font-family: 'Roboto', 'Noto', sans-serif;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
} margin-bottom: 123px;
#init div {
line-height: 34px;
margin-bottom: 89px;
} }
</style> </style>
</head> </head>
<body fullbleed> <body fullbleed>
<div id='init'> <div id='init'><img src='/static/favicon-192x192.png' height='192'></div>
<img src='/static/splash.png' height='230' />
<div>Initializing</div>
</div>
<script src='/static/webcomponents-lite.min.js'></script> <script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' /> <link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant> <home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "dff74f773ea8b0356b0bd8130ed6f0cf" VERSION = "ece94598f1575599c5aefa7322b1423b"

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 39e09d85b74afb332ad2872b5aa556c9c9d113c3 Subproject commit c130933f45262dd1c7b4fd75d23a90c132fb271f

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -3,12 +3,17 @@
"short_name": "Assistant", "short_name": "Assistant",
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"theme_color": "#03A9F4",
"icons": [ "icons": [
{ {
"src": "\/static\/favicon-192x192.png", "src": "/static/favicon-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image\/png", "type": "image/png",
"density": "4.0" },
{
"src": "/static/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png",
} }
] ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,6 @@ from homeassistant.const import (
STATE_UNKNOWN) STATE_UNKNOWN)
DOMAIN = "group" DOMAIN = "group"
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"

View File

@ -12,10 +12,7 @@ import logging
import time import time
import gzip import gzip
import os import os
import random
import string
from datetime import timedelta from datetime import timedelta
from homeassistant.util import Throttle
from http.server import SimpleHTTPRequestHandler, HTTPServer from http.server import SimpleHTTPRequestHandler, HTTPServer
from http import cookies from http import cookies
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
@ -34,7 +31,6 @@ import homeassistant.util.dt as date_util
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
DOMAIN = "http" DOMAIN = "http"
DEPENDENCIES = []
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host" CONF_SERVER_HOST = "server_host"
@ -45,40 +41,30 @@ CONF_SESSIONS_ENABLED = "sessions_enabled"
DATA_API_PASSWORD = 'api_password' DATA_API_PASSWORD = 'api_password'
# Throttling time in seconds for expired sessions check # Throttling time in seconds for expired sessions check
MIN_SEC_SESSION_CLEARING = timedelta(seconds=20) SESSION_CLEAR_INTERVAL = timedelta(seconds=20)
SESSION_TIMEOUT_SECONDS = 1800 SESSION_TIMEOUT_SECONDS = 1800
SESSION_KEY = 'sessionId' SESSION_KEY = 'sessionId'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup(hass, config=None): def setup(hass, config):
""" Sets up the HTTP API and debug interface. """ """ Sets up the HTTP API and debug interface. """
if config is None or DOMAIN not in config: conf = config[DOMAIN]
config = {DOMAIN: {}}
api_password = util.convert(config[DOMAIN].get(CONF_API_PASSWORD), str) api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
no_password_set = api_password is None
if no_password_set:
api_password = util.get_random_string()
# If no server host is given, accept all incoming requests # If no server host is given, accept all incoming requests
server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0') server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
server_port = config[DOMAIN].get(CONF_SERVER_PORT, SERVER_PORT) development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
development = str(config[DOMAIN].get(CONF_DEVELOPMENT, "")) == "1"
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
try: try:
server = HomeAssistantHTTPServer( server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password, (server_host, server_port), RequestHandler, hass, api_password,
development, no_password_set, sessions_enabled) development)
except OSError: except OSError:
# Happens if address already in use # If address already in use
_LOGGER.exception("Error setting up HTTP server") _LOGGER.exception("Error setting up HTTP server")
return False return False
@ -103,17 +89,15 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class, def __init__(self, server_address, request_handler_class,
hass, api_password, development, no_password_set, hass, api_password, development):
sessions_enabled):
super().__init__(server_address, request_handler_class) super().__init__(server_address, request_handler_class)
self.server_address = server_address self.server_address = server_address
self.hass = hass self.hass = hass
self.api_password = api_password self.api_password = api_password
self.development = development self.development = development
self.no_password_set = no_password_set
self.paths = [] self.paths = []
self.sessions = SessionStore(sessions_enabled) self.sessions = SessionStore()
# We will lazy init this one if needed # We will lazy init this one if needed
self.event_forwarder = None self.event_forwarder = None
@ -162,12 +146,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, client_addr, server): def __init__(self, req, client_addr, server):
""" Contructor, call the base constructor and set up session """ """ Contructor, call the base constructor and set up session """
self._session = None # Track if this was an authenticated request
self.authenticated = False
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments): def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """ """ Redirect built-in log to HA logging """
if self.server.no_password_set: if self.server.api_password is None:
_LOGGER.info(fmt, *arguments) _LOGGER.info(fmt, *arguments)
else: else:
_LOGGER.info( _LOGGER.info(
@ -202,18 +187,17 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY) "Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return return
self._session = self.get_session() if self.server.api_password is None:
if self.server.no_password_set: self.authenticated = True
api_password = self.server.api_password elif HTTP_HEADER_HA_AUTH in self.headers:
else:
api_password = self.headers.get(HTTP_HEADER_HA_AUTH) api_password = self.headers.get(HTTP_HEADER_HA_AUTH)
if not api_password and DATA_API_PASSWORD in data: if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD] api_password = data[DATA_API_PASSWORD]
if not api_password and self._session is not None: self.authenticated = api_password == self.server.api_password
api_password = self._session.cookie_values.get( else:
CONF_API_PASSWORD) self.authenticated = self.verify_session()
if '_METHOD' in data: if '_METHOD' in data:
method = data.pop('_METHOD') method = data.pop('_METHOD')
@ -246,16 +230,11 @@ class RequestHandler(SimpleHTTPRequestHandler):
# Did we find a handler for the incoming request? # Did we find a handler for the incoming request?
if handle_request_method: if handle_request_method:
# For some calls we need a valid password # For some calls we need a valid password
if require_auth and api_password != self.server.api_password: if require_auth and not self.authenticated:
self.write_json_message( self.write_json_message(
"API password missing or incorrect.", HTTP_UNAUTHORIZED) "API password missing or incorrect.", HTTP_UNAUTHORIZED)
return
else:
if self._session is None and require_auth:
self._session = self.server.sessions.create(
api_password)
handle_request_method(self, path_match, data) handle_request_method(self, path_match, data)
@ -308,18 +287,19 @@ class RequestHandler(SimpleHTTPRequestHandler):
json.dumps(data, indent=4, sort_keys=True, json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8")) cls=rem.JSONEncoder).encode("UTF-8"))
def write_file(self, path): def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """ """ Returns a file to the user. """
try: try:
with open(path, 'rb') as inp: with open(path, 'rb') as inp:
self.write_file_pointer(self.guess_type(path), inp) self.write_file_pointer(self.guess_type(path), inp,
cache_headers)
except IOError: except IOError:
self.send_response(HTTP_NOT_FOUND) self.send_response(HTTP_NOT_FOUND)
self.end_headers() self.end_headers()
_LOGGER.exception("Unable to serve %s", path) _LOGGER.exception("Unable to serve %s", path)
def write_file_pointer(self, content_type, inp): def write_file_pointer(self, content_type, inp, cache_headers=True):
""" """
Helper function to write a file pointer to the user. Helper function to write a file pointer to the user.
Does not do error handling. Does not do error handling.
@ -329,6 +309,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.send_response(HTTP_OK) self.send_response(HTTP_OK)
self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type) self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type)
if cache_headers:
self.set_cache_header() self.set_cache_header()
self.set_session_cookie_header() self.set_session_cookie_header()
@ -356,7 +337,9 @@ class RequestHandler(SimpleHTTPRequestHandler):
def set_cache_header(self): def set_cache_header(self):
""" Add cache headers if not in development """ """ Add cache headers if not in development """
if not self.server.development: if self.server.development:
return
# 1 year in seconds # 1 year in seconds
cache_time = 365 * 86400 cache_time = 365 * 86400
@ -368,63 +351,67 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.date_time_string(time.time()+cache_time)) self.date_time_string(time.time()+cache_time))
def set_session_cookie_header(self): def set_session_cookie_header(self):
""" Add the header for the session cookie """ """ Add the header for the session cookie and return session id. """
if self.server.sessions.enabled and self._session is not None: if not self.authenticated:
existing_sess_id = self.get_current_session_id() return
session_id = self.get_cookie_session_id()
if session_id is not None:
self.server.sessions.extend_validation(session_id)
return
if existing_sess_id != self._session.session_id:
self.send_header( self.send_header(
'Set-Cookie', 'Set-Cookie',
SESSION_KEY+'='+self._session.session_id) '{}={}'.format(SESSION_KEY, self.server.sessions.create())
)
def get_session(self): return session_id
""" Get the requested session object from cookie value """
if self.server.sessions.enabled is not True:
return None
session_id = self.get_current_session_id() def verify_session(self):
if session_id is not None: """ Verify that we are in a valid session. """
session = self.server.sessions.get(session_id) return self.get_cookie_session_id() is not None
if session is not None:
session.reset_expiry()
return session
return None def get_cookie_session_id(self):
def get_current_session_id(self):
""" """
Extracts the current session id from the Extracts the current session id from the
cookie or returns None if not set cookie or returns None if not set or invalid
""" """
if 'Cookie' not in self.headers:
return None
cookie = cookies.SimpleCookie() cookie = cookies.SimpleCookie()
try:
cookie.load(self.headers["Cookie"])
except cookies.CookieError:
return None
if self.headers.get('Cookie', None) is not None: morsel = cookie.get(SESSION_KEY)
cookie.load(self.headers.get("Cookie"))
if cookie.get(SESSION_KEY, False): if morsel is None:
return cookie[SESSION_KEY].value return None
session_id = cookie[SESSION_KEY].value
if self.server.sessions.is_valid(session_id):
return session_id
return None return None
def destroy_session(self):
""" Destroys session. """
session_id = self.get_cookie_session_id()
class ServerSession: if session_id is None:
""" A very simple session class """ return
def __init__(self, session_id):
""" Set up the expiry time on creation """
self._expiry = 0
self.reset_expiry()
self.cookie_values = {}
self.session_id = session_id
def reset_expiry(self): self.send_header('Set-Cookie', '')
""" Resets the expiry based on current time """ self.server.sessions.destroy(session_id)
self._expiry = date_util.utcnow() + timedelta(
seconds=SESSION_TIMEOUT_SECONDS)
@property
def is_expired(self): def session_valid_time():
""" Return true if the session is expired based on the expiry time """ """ Time till when a session will be valid. """
return self._expiry < date_util.utcnow() return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS)
class SessionStore(object): class SessionStore(object):
@ -432,47 +419,42 @@ class SessionStore(object):
def __init__(self, enabled=True): def __init__(self, enabled=True):
""" Set up the session store """ """ Set up the session store """
self._sessions = {} self._sessions = {}
self.enabled = enabled self.lock = threading.RLock()
self.session_lock = threading.RLock()
@Throttle(MIN_SEC_SESSION_CLEARING) @util.Throttle(SESSION_CLEAR_INTERVAL)
def remove_expired(self): def _remove_expired(self):
""" Remove any expired sessions. """ """ Remove any expired sessions. """
if self.session_lock.acquire(False): now = date_util.utcnow()
try: for key in [key for key, valid_time in self._sessions.items()
keys = [] if valid_time < now]:
for key in self._sessions.keys(): self._sessions.pop(key)
keys.append(key)
for key in keys: def is_valid(self, key):
if self._sessions[key].is_expired: """ Return True if a valid session is given. """
del self._sessions[key] with self.lock:
_LOGGER.info("Cleared expired session %s", key) self._remove_expired()
finally:
self.session_lock.release()
def add(self, key, session): return (key in self._sessions and
""" Add a new session to the list of tracked sessions """ self._sessions[key] > date_util.utcnow())
self.remove_expired()
with self.session_lock:
self._sessions[key] = session
def get(self, key): def extend_validation(self, key):
""" get a session by key """ """ Extend a session validation time. """
self.remove_expired() with self.lock:
session = self._sessions.get(key, None) self._sessions[key] = session_valid_time()
if session is not None and session.is_expired:
return None
return session
def create(self, api_password): def destroy(self, key):
""" Creates a new session and adds it to the sessions """ """ Destroy a session by key. """
if self.enabled is not True: with self.lock:
return None self._sessions.pop(key, None)
chars = string.ascii_letters + string.digits def create(self):
session_id = ''.join([random.choice(chars) for i in range(20)]) """ Creates a new session. """
session = ServerSession(session_id) with self.lock:
session.cookie_values[CONF_API_PASSWORD] = api_password session_id = util.get_random_string(20)
self.add(session_id, session)
return session while session_id in self._sessions:
session_id = util.get_random_string(20)
self._sessions[session_id] = session_valid_time()
return session_id

View File

@ -22,13 +22,11 @@ ATTR_VALUE1 = 'value1'
ATTR_VALUE2 = 'value2' ATTR_VALUE2 = 'value2'
ATTR_VALUE3 = 'value3' ATTR_VALUE3 = 'value3'
DEPENDENCIES = []
REQUIREMENTS = ['pyfttt==0.3'] REQUIREMENTS = ['pyfttt==0.3']
def trigger(hass, event, value1=None, value2=None, value3=None): def trigger(hass, event, value1=None, value2=None, value3=None):
""" Trigger a Maker IFTTT recipe """ """ Trigger a Maker IFTTT recipe. """
data = { data = {
ATTR_EVENT: event, ATTR_EVENT: event,
ATTR_VALUE1: value1, ATTR_VALUE1: value1,
@ -39,7 +37,7 @@ def trigger(hass, event, value1=None, value2=None, value3=None):
def setup(hass, config): def setup(hass, config):
""" Setup the ifttt service component """ """ Setup the ifttt service component. """
if not validate_config(config, {DOMAIN: ['key']}, _LOGGER): if not validate_config(config, {DOMAIN: ['key']}, _LOGGER):
return False return False

View File

@ -9,7 +9,6 @@ https://home-assistant.io/components/introduction/
import logging import logging
DOMAIN = 'introduction' DOMAIN = 'introduction'
DEPENDENCIES = []
def setup(hass, config=None): def setup(hass, config=None):

View File

@ -20,7 +20,6 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME) ATTR_FRIENDLY_NAME)
DOMAIN = "isy994" DOMAIN = "isy994"
DEPENDENCIES = []
REQUIREMENTS = ['PyISY==1.0.5'] REQUIREMENTS = ['PyISY==1.0.5']
DISCOVER_LIGHTS = "isy994.lights" DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches" DISCOVER_SWITCHES = "isy994.switches"
@ -117,7 +116,6 @@ class ISYDeviceABC(ToggleEntity):
def __init__(self, node): def __init__(self, node):
# setup properties # setup properties
self.node = node self.node = node
self.hidden = HIDDEN_STRING in self.raw_name
# track changes # track changes
self._change_handler = self.node.status. \ self._change_handler = self.node.status. \
@ -182,6 +180,11 @@ class ISYDeviceABC(ToggleEntity):
return self.raw_name.replace(HIDDEN_STRING, '').strip() \ return self.raw_name.replace(HIDDEN_STRING, '').strip() \
.replace('_', ' ') .replace('_', ' ')
@property
def hidden(self):
""" Suggestion if the entity should be hidden from UIs. """
return HIDDEN_STRING in self.raw_name
def update(self): def update(self):
""" Update state of the sensor. """ """ Update state of the sensor. """
# ISY objects are automatically updated by the ISY's event stream # ISY objects are automatically updated by the ISY's event stream

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
DOMAIN = "keyboard" DOMAIN = "keyboard"
DEPENDENCIES = []
REQUIREMENTS = ['pyuserinput==0.1.9'] REQUIREMENTS = ['pyuserinput==0.1.9']

View File

@ -21,7 +21,6 @@ import homeassistant.util.color as color_util
DOMAIN = "light" DOMAIN = "light"
DEPENDENCIES = []
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
GROUP_NAME_ALL_LIGHTS = 'all lights' GROUP_NAME_ALL_LIGHTS = 'all lights'

View File

@ -14,7 +14,6 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ["blinkstick==1.1.7"] REQUIREMENTS = ["blinkstick==1.1.7"]
DEPENDENCIES = []
# pylint: disable=unused-argument # pylint: disable=unused-argument

View File

@ -206,9 +206,7 @@ class HueLight(Light):
command = {'on': True} command = {'on': True}
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
# Transition time is in 1/10th seconds and cannot exceed command['transitiontime'] = kwargs[ATTR_TRANSITION] * 10
# 900 seconds.
command['transitiontime'] = min(9000, kwargs[ATTR_TRANSITION] * 10)
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
command['bri'] = kwargs[ATTR_BRIGHTNESS] command['bri'] = kwargs[ATTR_BRIGHTNESS]

View File

@ -8,171 +8,277 @@ https://home-assistant.io/components/light.limitlessled/
""" """
import logging import logging
from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
ATTR_RGB_COLOR, ATTR_EFFECT, ATTR_RGB_COLOR, ATTR_EFFECT,
ATTR_COLOR_TEMP, ATTR_TRANSITION,
ATTR_FLASH, FLASH_LONG,
EFFECT_COLORLOOP, EFFECT_WHITE) EFFECT_COLORLOOP, EFFECT_WHITE)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['ledcontroller==1.1.0'] REQUIREMENTS = ['limitlessled==1.0.0']
RGB_BOUNDARY = 40
COLOR_TABLE = { DEFAULT_TRANSITION = 0
'white': [0xFF, 0xFF, 0xFF], DEFAULT_PORT = 8899
'violet': [0xEE, 0x82, 0xEE], DEFAULT_VERSION = 5
'royal_blue': [0x41, 0x69, 0xE1], DEFAULT_LED_TYPE = 'rgbw'
'baby_blue': [0x87, 0xCE, 0xFA], WHITE = [255, 255, 255]
'aqua': [0x00, 0xFF, 0xFF],
'royal_mint': [0x7F, 0xFF, 0xD4],
'seafoam_green': [0x2E, 0x8B, 0x57],
'green': [0x00, 0x80, 0x00],
'lime_green': [0x32, 0xCD, 0x32],
'yellow': [0xFF, 0xFF, 0x00],
'yellow_orange': [0xDA, 0xA5, 0x20],
'orange': [0xFF, 0xA5, 0x00],
'red': [0xFF, 0x00, 0x00],
'pink': [0xFF, 0xC0, 0xCB],
'fusia': [0xFF, 0x00, 0xFF],
'lilac': [0xDA, 0x70, 0xD6],
'lavendar': [0xE6, 0xE6, 0xFA],
}
def _distance_squared(rgb1, rgb2): def rewrite_legacy(config):
""" Return sum of squared distances of each color part. """ """ Rewrite legacy configuration to new format. """
return sum((val1-val2)**2 for val1, val2 in zip(rgb1, rgb2)) bridges = config.get('bridges', [config])
new_bridges = []
for bridge_conf in bridges:
def _rgb_to_led_color(rgb_color): groups = []
""" Convert an RGB color to the closest color string and color. """ if 'groups' in bridge_conf:
return sorted((_distance_squared(rgb_color, color), name) groups = bridge_conf['groups']
for name, color in COLOR_TABLE.items())[0][1] else:
_LOGGER.warning("Legacy configuration format detected")
for i in range(1, 5):
name_key = 'group_%d_name' % i
if name_key in bridge_conf:
groups.append({
'number': i,
'type': bridge_conf.get('group_%d_type' % i,
DEFAULT_LED_TYPE),
'name': bridge_conf.get(name_key)
})
new_bridges.append({
'host': bridge_conf.get('host'),
'groups': groups
})
return {'bridges': new_bridges}
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the LimitlessLED lights. """ """ Gets the LimitlessLED lights. """
import ledcontroller from limitlessled.bridge import Bridge
# Handle old configuration format: # Two legacy configuration formats are supported to
bridges = config.get('bridges', [config]) # maintain backwards compatibility.
config = rewrite_legacy(config)
for bridge_id, bridge in enumerate(bridges):
bridge['id'] = bridge_id
pool = ledcontroller.LedControllerPool([x['host'] for x in bridges])
# Use the expanded configuration format.
lights = [] lights = []
for bridge in bridges: for bridge_conf in config.get('bridges'):
for i in range(1, 5): bridge = Bridge(bridge_conf.get('host'),
name_key = 'group_%d_name' % i port=bridge_conf.get('port', DEFAULT_PORT),
if name_key in bridge: version=bridge_conf.get('version', DEFAULT_VERSION))
group_type = bridge.get('group_%d_type' % i, 'rgbw') for group_conf in bridge_conf.get('groups'):
lights.append(LimitlessLED.factory(pool, bridge['id'], i, group = bridge.add_group(group_conf.get('number'),
bridge[name_key], group_conf.get('name'),
group_type)) group_conf.get('type', DEFAULT_LED_TYPE))
lights.append(LimitlessLEDGroup.factory(group))
add_devices_callback(lights) add_devices_callback(lights)
class LimitlessLED(Light): def state(new_state):
""" Represents a LimitlessLED light """ """ State decorator.
Specify True (turn on) or False (turn off).
"""
def decorator(function):
""" Decorator function. """
# pylint: disable=no-member,protected-access
def wrapper(self, **kwargs):
""" Wrap a group state change. """
from limitlessled.pipeline import Pipeline
pipeline = Pipeline()
transition_time = DEFAULT_TRANSITION
# Stop any repeating pipeline.
if self.repeating:
self.repeating = False
self.group.stop()
# Not on and should be? Turn on.
if not self.is_on and new_state is True:
pipeline.on()
# Set transition time.
if ATTR_TRANSITION in kwargs:
transition_time = kwargs[ATTR_TRANSITION]
# Do group type-specific work.
function(self, transition_time, pipeline, **kwargs)
# Update state.
self._is_on = new_state
self.group.enqueue(pipeline)
self.update_ha_state()
return wrapper
return decorator
class LimitlessLEDGroup(Light):
""" LimitessLED group. """
def __init__(self, group):
""" Initialize a group. """
self.group = group
self.repeating = False
self._is_on = False
self._brightness = None
@staticmethod @staticmethod
def factory(pool, controller_id, group, name, group_type): def factory(group):
''' Construct a Limitless LED of the appropriate type ''' """ Produce LimitlessLEDGroup objects. """
if group_type == 'white': from limitlessled.group.rgbw import RgbwGroup
return WhiteLimitlessLED(pool, controller_id, group, name) from limitlessled.group.white import WhiteGroup
elif group_type == 'rgbw': if isinstance(group, WhiteGroup):
return RGBWLimitlessLED(pool, controller_id, group, name) return LimitlessLEDWhiteGroup(group)
elif isinstance(group, RgbwGroup):
# pylint: disable=too-many-arguments return LimitlessLEDRGBWGroup(group)
def __init__(self, pool, controller_id, group, name, group_type):
self.pool = pool
self.controller_id = controller_id
self.group = group
self.pool.execute(self.controller_id, "set_group_type", self.group,
group_type)
# LimitlessLEDs don't report state, we have track it ourselves.
self.pool.execute(self.controller_id, "off", self.group)
self._name = name or DEVICE_DEFAULT_NAME
self._state = False
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed. """ """ No polling needed.
LimitlessLED state cannot be fetched.
"""
return False return False
@property @property
def name(self): def name(self):
""" Returns the name of the device if any. """ """ Returns the name of the group. """
return self._name return self.group.name
@property @property
def is_on(self): def is_on(self):
""" True if device is on. """ """ True if device is on. """
return self._state return self._is_on
def turn_off(self, **kwargs):
""" Turn the device off. """
self._state = False
self.pool.execute(self.controller_id, "off", self.group)
self.update_ha_state()
class RGBWLimitlessLED(LimitlessLED):
""" Represents a RGBW LimitlessLED light """
def __init__(self, pool, controller_id, group, name):
super().__init__(pool, controller_id, group, name, 'rgbw')
self._brightness = 100
self._led_color = 'white'
@property @property
def brightness(self): def brightness(self):
""" Brightness property. """
return self._brightness return self._brightness
@state(False)
def turn_off(self, transition_time, pipeline, **kwargs):
""" Turn off a group. """
if self.is_on:
pipeline.transition(transition_time, brightness=0.0).off()
class LimitlessLEDWhiteGroup(LimitlessLEDGroup):
""" LimitlessLED White group. """
def __init__(self, group):
""" Initialize White group. """
super().__init__(group)
# Initialize group with known values.
self.group.on = True
self.group.temperature = 1.0
self.group.brightness = 0.0
self._brightness = _to_hass_brightness(1.0)
self._temperature = _to_hass_temperature(self.group.temperature)
self.group.on = False
@property
def color_temp(self):
""" Temperature property. """
return self._temperature
@state(True)
def turn_on(self, transition_time, pipeline, **kwargs):
""" Turn on (or adjust property of) a group. """
# Check arguments.
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs:
self._temperature = kwargs[ATTR_COLOR_TEMP]
# Set up transition.
pipeline.transition(transition_time,
brightness=_from_hass_brightness(
self._brightness),
temperature=_from_hass_temperature(
self._temperature))
class LimitlessLEDRGBWGroup(LimitlessLEDGroup):
""" LimitlessLED RGBW group. """
def __init__(self, group):
""" Initialize RGBW group. """
super().__init__(group)
# Initialize group with known values.
self.group.on = True
self.group.white()
self._color = WHITE
self.group.brightness = 0.0
self._brightness = _to_hass_brightness(1.0)
self.group.on = False
@property @property
def rgb_color(self): def rgb_color(self):
return COLOR_TABLE[self._led_color] """ Color property. """
return self._color
def turn_on(self, **kwargs):
""" Turn the device on. """
self._state = True
@state(True)
def turn_on(self, transition_time, pipeline, **kwargs):
""" Turn on (or adjust property of) a group. """
from limitlessled.presets import COLORLOOP
# Check arguments.
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS] self._brightness = kwargs[ATTR_BRIGHTNESS]
if ATTR_RGB_COLOR in kwargs: if ATTR_RGB_COLOR in kwargs:
self._led_color = _rgb_to_led_color(kwargs[ATTR_RGB_COLOR]) self._color = kwargs[ATTR_RGB_COLOR]
# White is a special case.
effect = kwargs.get(ATTR_EFFECT) if min(self._color) > 256 - RGB_BOUNDARY:
pipeline.white()
if effect == EFFECT_COLORLOOP: self._color = WHITE
self.pool.execute(self.controller_id, "disco", self.group) # Set up transition.
elif effect == EFFECT_WHITE: pipeline.transition(transition_time,
self.pool.execute(self.controller_id, "white", self.group) brightness=_from_hass_brightness(
else: self._brightness),
self.pool.execute(self.controller_id, "set_color", color=_from_hass_color(self._color))
self._led_color, self.group) # Flash.
if ATTR_FLASH in kwargs:
# Brightness can be set independently of color duration = 0
self.pool.execute(self.controller_id, "set_brightness", if kwargs[ATTR_FLASH] == FLASH_LONG:
self._brightness / 255.0, self.group) duration = 1
pipeline.flash(duration=duration)
self.update_ha_state() # Add effects.
if ATTR_EFFECT in kwargs:
if kwargs[ATTR_EFFECT] == EFFECT_COLORLOOP:
self.repeating = True
pipeline.append(COLORLOOP)
if kwargs[ATTR_EFFECT] == EFFECT_WHITE:
pipeline.white()
self._color = WHITE
class WhiteLimitlessLED(LimitlessLED): def _from_hass_temperature(temperature):
""" Represents a White LimitlessLED light """ """ Convert Home Assistant color temperature
units to percentage.
"""
return (temperature - 154) / 346
def __init__(self, pool, controller_id, group, name):
super().__init__(pool, controller_id, group, name, 'white')
def turn_on(self, **kwargs): def _to_hass_temperature(temperature):
""" Turn the device on. """ """ Convert percentage to Home Assistant
self._state = True color temperature units.
self.pool.execute(self.controller_id, "on", self.group) """
self.update_ha_state() return int(temperature * 346) + 154
def _from_hass_brightness(brightness):
""" Convert Home Assistant brightness units
to percentage.
"""
return brightness / 255
def _to_hass_brightness(brightness):
""" Convert percentage to Home Assistant
brightness units.
"""
return int(brightness * 255)
def _from_hass_color(color):
""" Convert Home Assistant RGB list
to Color tuple.
"""
from limitlessled import Color
return Color(*tuple(color))
def _to_hass_color(color):
""" Convert from Color tuple to
Home Assistant RGB list.
"""
return list([int(c) for c in color])

View File

@ -8,7 +8,6 @@ https://home-assistant.io/components/light.mqtt/
""" """
import logging import logging
import homeassistant.util.color as color_util
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (Light, from homeassistant.components.light import (Light,
ATTR_BRIGHTNESS, ATTR_RGB_COLOR) ATTR_BRIGHTNESS, ATTR_RGB_COLOR)
@ -19,7 +18,6 @@ DEFAULT_NAME = "MQTT Light"
DEFAULT_QOS = 0 DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = "on" DEFAULT_PAYLOAD_ON = "on"
DEFAULT_PAYLOAD_OFF = "off" DEFAULT_PAYLOAD_OFF = "off"
DEFAULT_RGB_PATTERN = "%d,%d,%d"
DEFAULT_OPTIMISTIC = False DEFAULT_OPTIMISTIC = False
DEPENDENCIES = ['mqtt'] DEPENDENCIES = ['mqtt']
@ -37,45 +35,40 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
add_devices_callback([MqttLight( add_devices_callback([MqttLight(
hass, hass,
config.get('name', DEFAULT_NAME), config.get('name', DEFAULT_NAME),
{"state_topic": config.get('state_topic'), {
"state_topic": config.get('state_topic'),
"command_topic": config.get('command_topic'), "command_topic": config.get('command_topic'),
"brightness_state_topic": config.get('brightness_state_topic'), "brightness_state_topic": config.get('brightness_state_topic'),
"brightness_command_topic": "brightness_command_topic": config.get('brightness_command_topic'),
config.get('brightness_command_topic'),
"rgb_state_topic": config.get('rgb_state_topic'), "rgb_state_topic": config.get('rgb_state_topic'),
"rgb_command_topic": config.get('rgb_command_topic')}, "rgb_command_topic": config.get('rgb_command_topic')
config.get('rgb', None), },
config.get('qos', DEFAULT_QOS), config.get('qos', DEFAULT_QOS),
{"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), {
"off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, "on": config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('brightness'), "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)
},
config.get('optimistic', DEFAULT_OPTIMISTIC))]) config.get('optimistic', DEFAULT_OPTIMISTIC))])
# pylint: disable=too-many-instance-attributes
class MqttLight(Light): class MqttLight(Light):
""" Provides a MQTT light. """ """ Provides a MQTT light. """
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(self, hass, name, def __init__(self, hass, name, topic, qos, payload, optimistic):
topic,
rgb, qos,
payload,
brightness, optimistic):
self._hass = hass self._hass = hass
self._name = name self._name = name
self._topic = topic self._topic = topic
self._rgb = rgb
self._qos = qos self._qos = qos
self._payload = payload self._payload = payload
self._brightness = brightness self._optimistic = optimistic or topic["state_topic"] is None
self._optimistic = optimistic self._optimistic_rgb = optimistic or topic["rgb_state_topic"] is None
self._optimistic_brightness = (optimistic or
topic["brightness_state_topic"] is None)
self._state = False self._state = False
self._xy = None
def message_received(topic, payload, qos): def state_received(topic, payload, qos):
""" A new MQTT message has been received. """ """ A new MQTT message has been received. """
if payload == self._payload["on"]: if payload == self._payload["on"]:
self._state = True self._state = True
@ -84,27 +77,15 @@ class MqttLight(Light):
self.update_ha_state() self.update_ha_state()
if self._topic["state_topic"] is None: if self._topic["state_topic"] is not None:
# force optimistic mode
self._optimistic = True
else:
# Subscribe the state_topic
mqtt.subscribe(self._hass, self._topic["state_topic"], mqtt.subscribe(self._hass, self._topic["state_topic"],
message_received, self._qos) state_received, self._qos)
def brightness_received(topic, payload, qos): def brightness_received(topic, payload, qos):
""" A new MQTT message for the brightness has been received. """ """ A new MQTT message for the brightness has been received. """
self._brightness = int(payload) self._brightness = int(payload)
self.update_ha_state() self.update_ha_state()
def rgb_received(topic, payload, qos):
""" A new MQTT message has been received. """
self._rgb = [int(val) for val in payload.split(',')]
self._xy = color_util.color_RGB_to_xy(int(self._rgb[0]),
int(self._rgb[1]),
int(self._rgb[2]))
self.update_ha_state()
if self._topic["brightness_state_topic"] is not None: if self._topic["brightness_state_topic"] is not None:
mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], mqtt.subscribe(self._hass, self._topic["brightness_state_topic"],
brightness_received, self._qos) brightness_received, self._qos)
@ -112,12 +93,17 @@ class MqttLight(Light):
else: else:
self._brightness = None self._brightness = None
def rgb_received(topic, payload, qos):
""" A new MQTT message has been received. """
self._rgb = [int(val) for val in payload.split(',')]
self.update_ha_state()
if self._topic["rgb_state_topic"] is not None: if self._topic["rgb_state_topic"] is not None:
mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], mqtt.subscribe(self._hass, self._topic["rgb_state_topic"],
rgb_received, self._qos) rgb_received, self._qos)
self._xy = [0, 0] self._rgb = [255, 255, 255]
else: else:
self._xy = None self._rgb = None
@property @property
def brightness(self): def brightness(self):
@ -129,11 +115,6 @@ class MqttLight(Light):
""" RGB color value. """ """ RGB color value. """
return self._rgb return self._rgb
@property
def color_xy(self):
""" RGB color value. """
return self._xy
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed for a MQTT light. """ """ No polling needed for a MQTT light. """
@ -151,19 +132,26 @@ class MqttLight(Light):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
""" Turn the device on. """ """ Turn the device on. """
should_update = False
if ATTR_RGB_COLOR in kwargs and \ if ATTR_RGB_COLOR in kwargs and \
self._topic["rgb_command_topic"] is not None: self._topic["rgb_command_topic"] is not None:
self._rgb = kwargs[ATTR_RGB_COLOR]
rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb)
mqtt.publish(self._hass, self._topic["rgb_command_topic"], mqtt.publish(self._hass, self._topic["rgb_command_topic"],
rgb, self._qos) "{},{},{}".format(*kwargs[ATTR_RGB_COLOR]), self._qos)
if self._optimistic_rgb:
self._rgb = kwargs[ATTR_RGB_COLOR]
should_update = True
if ATTR_BRIGHTNESS in kwargs and \ if ATTR_BRIGHTNESS in kwargs and \
self._topic["brightness_command_topic"] is not None: self._topic["brightness_command_topic"] is not None:
self._brightness = kwargs[ATTR_BRIGHTNESS]
mqtt.publish(self._hass, self._topic["brightness_command_topic"], mqtt.publish(self._hass, self._topic["brightness_command_topic"],
self._brightness, self._qos) kwargs[ATTR_BRIGHTNESS], self._qos)
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
mqtt.publish(self._hass, self._topic["command_topic"], mqtt.publish(self._hass, self._topic["command_topic"],
self._payload["on"], self._qos) self._payload["on"], self._qos)
@ -171,6 +159,9 @@ class MqttLight(Light):
if self._optimistic: if self._optimistic:
# optimistically assume that switch has changed state # optimistically assume that switch has changed state
self._state = True self._state = True
should_update = True
if should_update:
self.update_ha_state() self.update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):

View File

@ -12,6 +12,11 @@ import homeassistant.components.rfxtrx as rfxtrx
from homeassistant.components.light import Light from homeassistant.components.light import Light
from homeassistant.util import slugify 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
DEPENDENCIES = ['rfxtrx'] DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,12 +28,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lights = [] lights = []
devices = config.get('devices', None) devices = config.get('devices', None)
if devices: if devices:
for entity_id, entity_info in devices.items(): for entity_id, entity_info in devices.items():
if entity_id not in rfxtrx.RFX_DEVICES: if entity_id not in rfxtrx.RFX_DEVICES:
_LOGGER.info("Add %s rfxtrx.light", entity_info['name']) _LOGGER.info("Add %s rfxtrx.light", entity_info[ATTR_NAME])
rfxobject = rfxtrx.get_rfx_object(entity_info['packetid'])
new_light = RfxtrxLight(entity_info['name'], rfxobject, False) # Check if i must fire event
fire_event = entity_info.get(ATTR_FIREEVENT, False)
datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event}
rfxobject = rfxtrx.get_rfx_object(entity_info[ATTR_PACKETID])
new_light = RfxtrxLight(
entity_info[ATTR_NAME], rfxobject, datas
)
rfxtrx.RFX_DEVICES[entity_id] = new_light rfxtrx.RFX_DEVICES[entity_id] = new_light
lights.append(new_light) lights.append(new_light)
@ -54,12 +67,14 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
) )
pkt_id = "".join("{0:02x}".format(x) for x in event.data) pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, pkt_id) entity_name = "%s : %s" % (entity_id, pkt_id)
new_light = RfxtrxLight(entity_name, event, False) datas = {ATTR_STATE: False, ATTR_FIREEVENT: False}
new_light = RfxtrxLight(entity_name, event, datas)
rfxtrx.RFX_DEVICES[entity_id] = new_light rfxtrx.RFX_DEVICES[entity_id] = new_light
add_devices_callback([new_light]) add_devices_callback([new_light])
# Check if entity exists or previously added automatically # Check if entity exists or previously added automatically
if entity_id in rfxtrx.RFX_DEVICES: if entity_id in rfxtrx.RFX_DEVICES \
and isinstance(rfxtrx.RFX_DEVICES[entity_id], RfxtrxLight):
_LOGGER.debug( _LOGGER.debug(
"EntityID: %s light_update. Command: %s", "EntityID: %s light_update. Command: %s",
entity_id, entity_id,
@ -67,10 +82,22 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
) )
if event.values['Command'] == 'On'\ if event.values['Command'] == 'On'\
or event.values['Command'] == 'Off': or event.values['Command'] == 'Off':
if event.values['Command'] == 'On':
rfxtrx.RFX_DEVICES[entity_id].turn_on() # Update the rfxtrx device state
else: is_on = event.values['Command'] == 'On'
rfxtrx.RFX_DEVICES[entity_id].turn_off() # pylint: disable=protected-access
rfxtrx.RFX_DEVICES[entity_id]._state = is_on
rfxtrx.RFX_DEVICES[entity_id].update_ha_state()
# Fire event
if rfxtrx.RFX_DEVICES[entity_id].should_fire_event:
rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire(
EVENT_BUTTON_PRESSED, {
ATTR_ENTITY_ID:
rfxtrx.RFX_DEVICES[entity_id].entity_id,
ATTR_STATE: event.values['Command'].lower()
}
)
# Subscribe to main rfxtrx events # Subscribe to main rfxtrx events
if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
@ -79,10 +106,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class RfxtrxLight(Light): class RfxtrxLight(Light):
""" Provides a RFXtrx light. """ """ Provides a RFXtrx light. """
def __init__(self, name, event, state): def __init__(self, name, event, datas):
self._name = name self._name = name
self._event = event self._event = event
self._state = state self._state = datas[ATTR_STATE]
self._should_fire_event = datas[ATTR_FIREEVENT]
@property @property
def should_poll(self): def should_poll(self):
@ -94,6 +122,11 @@ class RfxtrxLight(Light):
""" Returns the name of the light if any. """ """ Returns the name of the light if any. """
return self._name return self._name
@property
def should_fire_event(self):
""" Returns is the device must fire event"""
return self._should_fire_event
@property @property
def is_on(self): def is_on(self):
""" True if light is on. """ """ True if light is on. """

View File

@ -10,6 +10,7 @@ from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
ATTR_FRIENDLY_NAME) ATTR_FRIENDLY_NAME)
REQUIREMENTS = ['tellcore-py==1.1.2'] REQUIREMENTS = ['tellcore-py==1.1.2']
SIGNAL_REPETITIONS = 1
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -21,13 +22,14 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
import tellcore.constants as tellcore_constants import tellcore.constants as tellcore_constants
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher()) core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
switches_and_lights = core.devices() switches_and_lights = core.devices()
lights = [] lights = []
for switch in switches_and_lights: for switch in switches_and_lights:
if switch.methods(tellcore_constants.TELLSTICK_DIM): if switch.methods(tellcore_constants.TELLSTICK_DIM):
lights.append(TellstickLight(switch)) lights.append(TellstickLight(switch, signal_repetitions))
def _device_event_callback(id_, method, data, cid): def _device_event_callback(id_, method, data, cid):
""" Called from the TelldusCore library to update one device """ """ Called from the TelldusCore library to update one device """
@ -52,11 +54,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class TellstickLight(Light): class TellstickLight(Light):
""" Represents a Tellstick light. """ """ Represents a Tellstick light. """
def __init__(self, tellstick_device): def __init__(self, tellstick_device, signal_repetitions):
import tellcore.constants as tellcore_constants import tellcore.constants as tellcore_constants
self.tellstick_device = tellstick_device self.tellstick_device = tellstick_device
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name} self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
self.signal_repetitions = signal_repetitions
self._brightness = 0 self._brightness = 0
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON | self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
@ -64,6 +67,7 @@ class TellstickLight(Light):
tellcore_constants.TELLSTICK_DIM | tellcore_constants.TELLSTICK_DIM |
tellcore_constants.TELLSTICK_UP | tellcore_constants.TELLSTICK_UP |
tellcore_constants.TELLSTICK_DOWN) tellcore_constants.TELLSTICK_DOWN)
self.update()
@property @property
def name(self): def name(self):
@ -82,6 +86,7 @@ class TellstickLight(Light):
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
""" Turns the switch off. """ """ Turns the switch off. """
for _ in range(self.signal_repetitions):
self.tellstick_device.turn_off() self.tellstick_device.turn_off()
self._brightness = 0 self._brightness = 0
self.update_ha_state() self.update_ha_state()
@ -95,6 +100,7 @@ class TellstickLight(Light):
else: else:
self._brightness = brightness self._brightness = brightness
for _ in range(self.signal_repetitions):
self.tellstick_device.dim(self._brightness) self.tellstick_device.dim(self._brightness)
self.update_ha_state() self.update_ha_state()

View File

@ -13,8 +13,8 @@ from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'9eb39eaba0717922815e673ad1114c685839d890.zip' '42fdcfa721b1bc583688e3592d8427f4c13ba6d9.zip'
'#python-wink==0.1.1'] '#python-wink==0.2']
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):

View File

@ -6,12 +6,13 @@ Support for Z-Wave lights.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.zwave/ https://home-assistant.io/components/light.zwave/
""" """
# Because we do not compile openzwave on CI
# pylint: disable=import-error # pylint: disable=import-error
import homeassistant.components.zwave as zwave from threading import Timer
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS) from homeassistant.components.light import (Light, ATTR_BRIGHTNESS)
from threading import Timer import homeassistant.components.zwave as zwave
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -0,0 +1,112 @@
"""
homeassistant.components.lock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with various locks that can be controlled remotely.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/lock/
"""
from datetime import timedelta
import logging
import os
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK,
ATTR_ENTITY_ID)
from homeassistant.components import (group, wink)
DOMAIN = 'lock'
SCAN_INTERVAL = 30
GROUP_NAME_ALL_LOCKS = 'all locks'
ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_LOCKED = "locked"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LOCKS: 'wink'
}
_LOGGER = logging.getLogger(__name__)
def is_locked(hass, entity_id=None):
""" Returns if the lock is locked based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_LOCKS
return hass.states.is_state(entity_id, STATE_LOCKED)
def lock(hass, entity_id=None):
""" Locks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_LOCK, data)
def unlock(hass, entity_id=None):
""" Unlocks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
def setup(hass, config):
""" Track states and offer events for locks. """
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_LOCKS)
component.setup(config)
def handle_lock_service(service):
""" Handles calls to the lock services. """
target_locks = component.extract_from_service(service)
for item in target_locks:
if service.service == SERVICE_LOCK:
item.lock()
else:
item.unlock()
if item.should_poll:
item.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_UNLOCK, handle_lock_service,
descriptions.get(SERVICE_UNLOCK))
hass.services.register(DOMAIN, SERVICE_LOCK, handle_lock_service,
descriptions.get(SERVICE_LOCK))
return True
class LockDevice(Entity):
""" Represents a lock within Home Assistant. """
# pylint: disable=no-self-use
@property
def is_locked(self):
""" Is the lock locked or unlocked. """
return None
def lock(self):
""" Locks the lock. """
raise NotImplementedError()
def unlock(self):
""" Unlocks the lock. """
raise NotImplementedError()
@property
def state(self):
locked = self.is_locked
if locked is None:
return STATE_UNKNOWN
return STATE_LOCKED if locked else STATE_UNLOCKED

View File

@ -0,0 +1,49 @@
"""
homeassistant.components.lock.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake locks.
"""
from homeassistant.components.lock import LockDevice
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo locks. """
add_devices_callback([
DemoLock('Front Door', STATE_LOCKED),
DemoLock('Kitchen Door', STATE_UNLOCKED)
])
class DemoLock(LockDevice):
""" Provides a demo lock. """
def __init__(self, name, state):
self._name = name
self._state = state
@property
def should_poll(self):
""" No polling needed for a demo lock. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def is_locked(self):
""" True if device is locked. """
return self._state == STATE_LOCKED
def lock(self, **kwargs):
""" Lock the device. """
self._state = STATE_LOCKED
self.update_ha_state()
def unlock(self, **kwargs):
""" Unlock the device. """
self._state = STATE_UNLOCKED
self.update_ha_state()

View File

@ -0,0 +1,68 @@
"""
homeassistant.components.lock.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.wink/
"""
import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'42fdcfa721b1bc583688e3592d8427f4c13ba6d9.zip'
'#python-wink==0.2']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
class WinkLockDevice(LockDevice):
""" Represents a Wink lock. """
def __init__(self, wink):
self.wink = wink
@property
def unique_id(self):
""" Returns the id of this wink lock """
return "{}.{}".format(self.__class__, self.wink.deviceId())
@property
def name(self):
""" Returns the name of the lock if any. """
return self.wink.name()
def update(self):
""" Update the state of the lock. """
self.wink.updateState()
@property
def is_locked(self):
""" True if device is locked. """
return self.wink.state()
def lock(self):
""" Lock the device. """
self.wink.setState(True)
def unlock(self):
""" Unlock the device. """
self.wink.setState(False)

View File

@ -10,7 +10,6 @@ import logging
from collections import OrderedDict from collections import OrderedDict
DOMAIN = 'logger' DOMAIN = 'logger'
DEPENDENCIES = []
LOGSEVERITY = { LOGSEVERITY = {
'CRITICAL': 50, 'CRITICAL': 50,

View File

@ -22,7 +22,6 @@ from homeassistant.const import (
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
DOMAIN = 'media_player' DOMAIN = 'media_player'
DEPENDENCIES = []
SCAN_INTERVAL = 10 SCAN_INTERVAL = 10
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'

View File

@ -49,7 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.info( _LOGGER.info(
'Device %s accessible and ready for control', device_id) 'Device %s accessible and ready for control', device_id)
else: else:
_LOGGER.warn( _LOGGER.warning(
'Device %s is not registered with firetv-server', device_id) 'Device %s is not registered with firetv-server', device_id)
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
_LOGGER.error('Could not connect to firetv-server at %s', host) _LOGGER.error('Could not connect to firetv-server at %s', host)

View File

@ -8,6 +8,8 @@ https://home-assistant.io/components/media_player.itunes/
""" """
import logging import logging
import requests
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE, MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE,
SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
@ -17,8 +19,6 @@ from homeassistant.components.media_player import (
from homeassistant.const import ( from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON) STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON)
import requests
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \

View File

@ -15,11 +15,6 @@ from homeassistant.components.media_player import (
from homeassistant.const import ( from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF) STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF)
try:
import jsonrpc_requests
except ImportError:
jsonrpc_requests = None
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['jsonrpc-requests==0.1'] REQUIREMENTS = ['jsonrpc-requests==0.1']
@ -31,11 +26,6 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the kodi platform. """ """ Sets up the kodi platform. """
global jsonrpc_requests # pylint: disable=invalid-name
if jsonrpc_requests is None:
import jsonrpc_requests as jsonrpc_requests_
jsonrpc_requests = jsonrpc_requests_
add_devices([ add_devices([
KodiDevice( KodiDevice(
config.get('name', 'Kodi'), config.get('name', 'Kodi'),
@ -60,6 +50,7 @@ class KodiDevice(MediaPlayerDevice):
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
def __init__(self, name, url, auth=None): def __init__(self, name, url, auth=None):
import jsonrpc_requests
self._name = name self._name = name
self._url = url self._url = url
self._server = jsonrpc_requests.Server(url, auth=auth) self._server = jsonrpc_requests.Server(url, auth=auth)
@ -77,6 +68,7 @@ class KodiDevice(MediaPlayerDevice):
def _get_players(self): def _get_players(self):
""" Returns the active player objects or None """ """ Returns the active player objects or None """
import jsonrpc_requests
try: try:
return self._server.Player.GetActivePlayers() return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError: except jsonrpc_requests.jsonrpc.TransportError:

View File

@ -39,9 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Sonos platform. """ """ Sets up the Sonos platform. """
import soco import soco
if discovery_info:
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))])
return True
players = soco.discover() players = soco.discover()
if not players: if not players:
_LOGGER.warning('No Sonos speakers found. Disabling: %s', __name__) _LOGGER.warning('No Sonos speakers found.')
return False return False
add_devices(SonosDevice(hass, p) for p in players) add_devices(SonosDevice(hass, p) for p in players)

View File

@ -13,7 +13,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
DOMAIN = "modbus" DOMAIN = "modbus"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/'
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0'] 'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0']

View File

@ -6,9 +6,12 @@ MQTT component, using paho-mqtt.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/ https://home-assistant.io/components/mqtt/
""" """
import json
import logging import logging
import os import os
import socket import socket
import time
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.util as util import homeassistant.util as util
@ -25,12 +28,12 @@ MQTT_CLIENT = None
DEFAULT_PORT = 1883 DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60 DEFAULT_KEEPALIVE = 60
DEFAULT_QOS = 0 DEFAULT_QOS = 0
DEFAULT_RETAIN = False
SERVICE_PUBLISH = 'publish' SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
DEPENDENCIES = [] REQUIREMENTS = ['paho-mqtt==1.1', 'jsonpath-rw==1.4.0']
REQUIREMENTS = ['paho-mqtt==1.1']
CONF_BROKER = 'broker' CONF_BROKER = 'broker'
CONF_PORT = 'port' CONF_PORT = 'port'
@ -43,9 +46,12 @@ CONF_CERTIFICATE = 'certificate'
ATTR_TOPIC = 'topic' ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload' ATTR_PAYLOAD = 'payload'
ATTR_QOS = 'qos' ATTR_QOS = 'qos'
ATTR_RETAIN = 'retain'
MAX_RECONNECT_WAIT = 300 # seconds
def publish(hass, topic, payload, qos=None): def publish(hass, topic, payload, qos=None, retain=None):
""" Send an MQTT message. """ """ Send an MQTT message. """
data = { data = {
ATTR_TOPIC: topic, ATTR_TOPIC: topic,
@ -53,6 +59,10 @@ def publish(hass, topic, payload, qos=None):
} }
if qos is not None: if qos is not None:
data[ATTR_QOS] = qos data[ATTR_QOS] = qos
if retain is not None:
data[ATTR_RETAIN] = retain
hass.services.call(DOMAIN, SERVICE_PUBLISH, data) hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
@ -65,8 +75,6 @@ def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
event.data[ATTR_QOS]) event.data[ATTR_QOS])
hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED, mqtt_topic_subscriber) hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED, mqtt_topic_subscriber)
if topic not in MQTT_CLIENT.topics:
MQTT_CLIENT.subscribe(topic, qos) MQTT_CLIENT.subscribe(topic, qos)
@ -116,9 +124,10 @@ def setup(hass, config):
msg_topic = call.data.get(ATTR_TOPIC) msg_topic = call.data.get(ATTR_TOPIC)
payload = call.data.get(ATTR_PAYLOAD) payload = call.data.get(ATTR_PAYLOAD)
qos = call.data.get(ATTR_QOS, DEFAULT_QOS) qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN)
if msg_topic is None or payload is None: if msg_topic is None or payload is None:
return return
MQTT_CLIENT.publish(msg_topic, payload, qos) MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
@ -127,44 +136,69 @@ def setup(hass, config):
return True return True
# pylint: disable=too-few-public-methods
class _JsonFmtParser(object):
""" Implements a JSON parser on xpath. """
def __init__(self, jsonpath):
import jsonpath_rw
self._expr = jsonpath_rw.parse(jsonpath)
def __call__(self, payload):
match = self._expr.find(json.loads(payload))
return match[0].value if len(match) > 0 else payload
# pylint: disable=too-few-public-methods
class FmtParser(object):
""" Wrapper for all supported formats. """
def __init__(self, fmt):
self._parse = lambda x: x
if fmt:
if fmt.startswith('json:'):
self._parse = _JsonFmtParser(fmt[5:])
def __call__(self, payload):
return self._parse(payload)
# This is based on one of the paho-mqtt examples: # This is based on one of the paho-mqtt examples:
# http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py # http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
class MQTT(object): # pragma: no cover class MQTT(object):
""" Implements messaging service for MQTT. """ """ Implements messaging service for MQTT. """
def __init__(self, hass, broker, port, client_id, keepalive, username, def __init__(self, hass, broker, port, client_id, keepalive, username,
password, certificate): password, certificate):
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
self.hass = hass self.userdata = {
self._progress = {} 'hass': hass,
self.topics = {} 'topics': {},
'progress': {},
}
if client_id is None: if client_id is None:
self._mqttc = mqtt.Client() self._mqttc = mqtt.Client()
else: else:
self._mqttc = mqtt.Client(client_id) self._mqttc = mqtt.Client(client_id)
self._mqttc.user_data_set(self.userdata)
if username is not None: if username is not None:
self._mqttc.username_pw_set(username, password) self._mqttc.username_pw_set(username, password)
if certificate is not None: if certificate is not None:
self._mqttc.tls_set(certificate) self._mqttc.tls_set(certificate)
self._mqttc.on_subscribe = self._mqtt_on_subscribe self._mqttc.on_subscribe = _mqtt_on_subscribe
self._mqttc.on_unsubscribe = self._mqtt_on_unsubscribe self._mqttc.on_unsubscribe = _mqtt_on_unsubscribe
self._mqttc.on_connect = self._mqtt_on_connect self._mqttc.on_connect = _mqtt_on_connect
self._mqttc.on_message = self._mqtt_on_message self._mqttc.on_disconnect = _mqtt_on_disconnect
self._mqttc.on_message = _mqtt_on_message
self._mqttc.connect(broker, port, keepalive) self._mqttc.connect(broker, port, keepalive)
def publish(self, topic, payload, qos): def publish(self, topic, payload, qos, retain):
""" Publish a MQTT message. """ """ Publish a MQTT message. """
self._mqttc.publish(topic, payload, qos) self._mqttc.publish(topic, payload, qos, retain)
def unsubscribe(self, topic):
""" Unsubscribe from topic. """
result, mid = self._mqttc.unsubscribe(topic)
_raise_on_error(result)
self._progress[mid] = topic
def start(self): def start(self):
""" Run the MQTT client. """ """ Run the MQTT client. """
@ -176,14 +210,30 @@ class MQTT(object): # pragma: no cover
def subscribe(self, topic, qos): def subscribe(self, topic, qos):
""" Subscribe to a topic. """ """ Subscribe to a topic. """
if topic in self.topics: if topic in self.userdata['topics']:
return return
result, mid = self._mqttc.subscribe(topic, qos) result, mid = self._mqttc.subscribe(topic, qos)
_raise_on_error(result) _raise_on_error(result)
self._progress[mid] = topic self.userdata['progress'][mid] = topic
self.topics[topic] = None self.userdata['topics'][topic] = None
def _mqtt_on_connect(self, mqttc, obj, flags, result_code): def unsubscribe(self, topic):
""" Unsubscribe from topic. """
result, mid = self._mqttc.unsubscribe(topic)
_raise_on_error(result)
self.userdata['progress'][mid] = topic
def _mqtt_on_message(mqttc, userdata, msg):
""" Message callback """
userdata['hass'].bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, {
ATTR_TOPIC: msg.topic,
ATTR_QOS: msg.qos,
ATTR_PAYLOAD: msg.payload.decode('utf-8'),
})
def _mqtt_on_connect(mqttc, userdata, flags, result_code):
""" On connect, resubscribe to all topics we were subscribed to. """ """ On connect, resubscribe to all topics we were subscribed to. """
if result_code != 0: if result_code != 0:
_LOGGER.error('Unable to connect to the MQTT broker: %s', { _LOGGER.error('Unable to connect to the MQTT broker: %s', {
@ -192,42 +242,64 @@ class MQTT(object): # pragma: no cover
3: 'Server unavailable', 3: 'Server unavailable',
4: 'Bad username or password', 4: 'Bad username or password',
5: 'Not authorised' 5: 'Not authorised'
}.get(result_code)) }.get(result_code, 'Unknown reason'))
self._mqttc.disconnect() mqttc.disconnect()
return return
old_topics = self.topics old_topics = userdata['topics']
self._progress = {}
self.topics = {} userdata['topics'] = {}
userdata['progress'] = {}
for topic, qos in old_topics.items(): for topic, qos in old_topics.items():
# qos is None if we were in process of subscribing # qos is None if we were in process of subscribing
if qos is not None: if qos is not None:
self._mqttc.subscribe(topic, qos) mqttc.subscribe(topic, qos)
def _mqtt_on_subscribe(self, mqttc, obj, mid, granted_qos):
""" Called when subscribe succesfull. """ def _mqtt_on_subscribe(mqttc, userdata, mid, granted_qos):
topic = self._progress.pop(mid, None) """ Called when subscribe successful. """
topic = userdata['progress'].pop(mid, None)
if topic is None: if topic is None:
return return
self.topics[topic] = granted_qos userdata['topics'][topic] = granted_qos
def _mqtt_on_unsubscribe(self, mqttc, obj, mid, granted_qos):
""" Called when subscribe succesfull. """ def _mqtt_on_unsubscribe(mqttc, userdata, mid, granted_qos):
topic = self._progress.pop(mid, None) """ Called when subscribe successful. """
topic = userdata['progress'].pop(mid, None)
if topic is None: if topic is None:
return return
self.topics.pop(topic, None) userdata['topics'].pop(topic, None)
def _mqtt_on_message(self, mqttc, obj, msg):
""" Message callback """
self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, {
ATTR_TOPIC: msg.topic,
ATTR_QOS: msg.qos,
ATTR_PAYLOAD: msg.payload.decode('utf-8'),
})
def _raise_on_error(result): # pragma: no cover def _mqtt_on_disconnect(mqttc, userdata, result_code):
""" Called when being disconnected. """
# When disconnected because of calling disconnect()
if result_code == 0:
return
tries = 0
wait_time = 0
while True:
try:
if mqttc.reconnect() == 0:
_LOGGER.info('Successfully reconnected to the MQTT server')
break
except socket.error:
pass
wait_time = min(2**tries, MAX_RECONNECT_WAIT)
_LOGGER.warning(
'Disconnected from MQTT (%s). Trying to reconnect in %ss',
result_code, wait_time)
# It is ok to sleep here as we are in the MQTT thread.
time.sleep(wait_time)
tries += 1
def _raise_on_error(result):
""" Raise error if error result. """ """ Raise error if error result. """
if result != 0: if result != 0:
raise HomeAssistantError('Error talking to MQTT: {}'.format(result)) raise HomeAssistantError('Error talking to MQTT: {}'.format(result))

View File

@ -17,7 +17,6 @@ from homeassistant.helpers import config_per_platform
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
DOMAIN = "notify" DOMAIN = "notify"
DEPENDENCIES = []
# Title of notification # Title of notification
ATTR_TITLE = "title" ATTR_TITLE = "title"

View File

@ -47,16 +47,16 @@ class PushBulletNotificationService(BaseNotificationService):
self.refresh() self.refresh()
def refresh(self): def refresh(self):
''' """
Refresh devices, contacts, etc Refresh devices, contacts, etc
pbtargets stores all targets available from this pushbullet instance pbtargets stores all targets available from this pushbullet instance
into a dict. These are PB objects!. It sacrifices a bit of memory into a dict. These are PB objects!. It sacrifices a bit of memory
for faster processing at send_message for faster processing at send_message.
As of sept 2015, contacts were replaced by chats. This is not As of sept 2015, contacts were replaced by chats. This is not
implemented in the module yet implemented in the module yet.
''' """
self.pushbullet.refresh() self.pushbullet.refresh()
self.pbtargets = { self.pbtargets = {
'device': { 'device': {
@ -72,7 +72,7 @@ class PushBulletNotificationService(BaseNotificationService):
If no target specified, a 'normal' push will be sent to all devices If no target specified, a 'normal' push will be sent to all devices
linked to the PB account. linked to the PB account.
Email is special, these are assumed to always exist. We use a special Email is special, these are assumed to always exist. We use a special
call which doesn't require a push object call which doesn't require a push object.
""" """
targets = kwargs.get(ATTR_TARGET) targets = kwargs.get(ATTR_TARGET)
title = kwargs.get(ATTR_TITLE) title = kwargs.get(ATTR_TITLE)
@ -100,7 +100,7 @@ class PushBulletNotificationService(BaseNotificationService):
# This also seems works to send to all devices in own account # This also seems works to send to all devices in own account
if ttype == 'email': if ttype == 'email':
self.pushbullet.push_note(title, message, email=tname) self.pushbullet.push_note(title, message, email=tname)
_LOGGER.info('Sent notification to self') _LOGGER.info('Sent notification to email %s', tname)
continue continue
# Refresh if name not found. While awaiting periodic refresh # Refresh if name not found. While awaiting periodic refresh
@ -108,18 +108,21 @@ class PushBulletNotificationService(BaseNotificationService):
if ttype not in self.pbtargets: if ttype not in self.pbtargets:
_LOGGER.error('Invalid target syntax: %s', target) _LOGGER.error('Invalid target syntax: %s', target)
continue continue
if tname.lower() not in self.pbtargets[ttype] and not refreshed:
tname = tname.lower()
if tname not in self.pbtargets[ttype] and not refreshed:
self.refresh() self.refresh()
refreshed = True refreshed = True
# Attempt push_note on a dict value. Keys are types & target # Attempt push_note on a dict value. Keys are types & target
# name. Dict pbtargets has all *actual* targets. # name. Dict pbtargets has all *actual* targets.
try: try:
self.pbtargets[ttype][tname.lower()].push_note(title, message) self.pbtargets[ttype][tname].push_note(title, message)
_LOGGER.info('Sent notification to %s/%s', ttype, tname)
except KeyError: except KeyError:
_LOGGER.error('No such target: %s/%s', ttype, tname) _LOGGER.error('No such target: %s/%s', ttype, tname)
continue continue
except self.pushbullet.errors.PushError: except self.pushbullet.errors.PushError:
_LOGGER.error('Notify failed to: %s/%s', ttype, tname) _LOGGER.error('Notify failed to: %s/%s', ttype, tname)
continue continue
_LOGGER.info('Sent notification to %s/%s', ttype, tname)

View File

@ -8,14 +8,14 @@ https://home-assistant.io/components/notify.xmpp/
""" """
import logging import logging
_LOGGER = logging.getLogger(__name__)
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.components.notify import ( from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService) DOMAIN, ATTR_TITLE, BaseNotificationService)
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0'] REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0']
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config): def get_service(hass, config):
""" Get the Jabber (XMPP) notification service. """ """ Get the Jabber (XMPP) notification service. """

View File

@ -23,7 +23,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
DOMAIN = "recorder" DOMAIN = "recorder"
DEPENDENCIES = []
DB_FILE = 'home-assistant.db' DB_FILE = 'home-assistant.db'
@ -59,7 +58,7 @@ def query_events(event_query, arguments=None):
def row_to_state(row): def row_to_state(row):
""" Convert a databsae row to a state. """ """ Convert a database row to a state. """
try: try:
return State( return State(
row[1], row[2], json.loads(row[3]), row[1], row[2], json.loads(row[3]),
@ -74,7 +73,7 @@ def row_to_state(row):
def row_to_event(row): def row_to_event(row):
""" Convert a databse row to an event. """ """ Convert a databse row to an event. """
try: try:
return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()], return Event(row[1], json.loads(row[2]), EventOrigin(row[3]),
date_util.utc_from_timestamp(row[5])) date_util.utc_from_timestamp(row[5]))
except ValueError: except ValueError:
# When json.loads fails # When json.loads fails
@ -83,8 +82,9 @@ def row_to_event(row):
def run_information(point_in_time=None): def run_information(point_in_time=None):
""" Returns information about current run or the run that """
covers point_in_time. """ Returns information about current run or the run that covers point_in_time.
"""
_verify_instance() _verify_instance()
if point_in_time is None or point_in_time > _INSTANCE.recording_start: if point_in_time is None or point_in_time > _INSTANCE.recording_start:
@ -142,8 +142,10 @@ class RecorderRun(object):
@property @property
def where_after_start_run(self): def where_after_start_run(self):
""" Returns SQL WHERE clause to select rows """
created after the start of the run. """ Returns SQL WHERE clause to select rows created after the start of the
run.
"""
return "created >= {} ".format(_adapt_datetime(self.start)) return "created >= {} ".format(_adapt_datetime(self.start))
@property @property
@ -158,9 +160,7 @@ class RecorderRun(object):
class Recorder(threading.Thread): class Recorder(threading.Thread):
""" """ Threaded recorder class """
Threaded recorder
"""
def __init__(self, hass): def __init__(self, hass):
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -208,8 +208,10 @@ class Recorder(threading.Thread):
self.queue.task_done() self.queue.task_done()
def event_listener(self, event): def event_listener(self, event):
""" Listens for new events on the EventBus and puts them """
in the process queue. """ Listens for new events on the EventBus and puts them in the process
queue.
"""
self.queue.put(event) self.queue.put(event)
def shutdown(self, event): def shutdown(self, event):
@ -433,6 +435,6 @@ def _adapt_datetime(datetimestamp):
def _verify_instance(): def _verify_instance():
""" throws error if recorder not initialized. """ """ Throws error if recorder not initialized. """
if _INSTANCE is None: if _INSTANCE is None:
raise RuntimeError("Recorder not initialized.") raise RuntimeError("Recorder not initialized.")

View File

@ -9,13 +9,20 @@ https://home-assistant.io/components/rfxtrx/
import logging import logging
from homeassistant.util import slugify from homeassistant.util import slugify
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' + REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' +
'#RFXtrx==0.2'] '#RFXtrx==0.2']
DOMAIN = "rfxtrx" DOMAIN = "rfxtrx"
CONF_DEVICE = 'device'
CONF_DEBUG = 'debug' ATTR_DEVICE = 'device'
ATTR_DEBUG = 'debug'
ATTR_STATE = 'state'
ATTR_NAME = 'name'
ATTR_PACKETID = 'packetid'
ATTR_FIREEVENT = 'fire_event'
EVENT_BUTTON_PRESSED = 'button_pressed'
RECEIVED_EVT_SUBSCRIBERS = [] RECEIVED_EVT_SUBSCRIBERS = []
RFX_DEVICES = {} RFX_DEVICES = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -50,15 +57,15 @@ def setup(hass, config):
# Init the rfxtrx module # Init the rfxtrx module
global RFXOBJECT global RFXOBJECT
if CONF_DEVICE not in config[DOMAIN]: if ATTR_DEVICE not in config[DOMAIN]:
_LOGGER.exception( _LOGGER.exception(
"can found device parameter in %s YAML configuration section", "can found device parameter in %s YAML configuration section",
DOMAIN DOMAIN
) )
return False return False
device = config[DOMAIN][CONF_DEVICE] device = config[DOMAIN][ATTR_DEVICE]
debug = config[DOMAIN].get(CONF_DEBUG, False) debug = config[DOMAIN].get(ATTR_DEBUG, False)
RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug) RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug)

View File

@ -0,0 +1,144 @@
"""
homeassistant.components.rollershutter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rollershutter component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter/
"""
import os
import logging
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.components import group
from homeassistant.const import (
SERVICE_MOVE_UP, SERVICE_MOVE_DOWN, SERVICE_STOP,
STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID)
DOMAIN = 'rollershutter'
SCAN_INTERVAL = 15
GROUP_NAME_ALL_ROLLERSHUTTERS = 'all rollershutters'
ENTITY_ID_ALL_ROLLERSHUTTERS = group.ENTITY_ID_FORMAT.format(
'all_rollershutters')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {}
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
def is_open(hass, entity_id=None):
""" Returns if the rollershutter is open based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_ROLLERSHUTTERS
return hass.states.is_state(entity_id, STATE_OPEN)
def move_up(hass, entity_id=None):
""" Move up all or specified rollershutter. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_MOVE_UP, data)
def move_down(hass, entity_id=None):
""" Move down all or specified rollershutter. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_MOVE_DOWN, data)
def stop(hass, entity_id=None):
""" Stops all or specified rollershutter. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_STOP, data)
def setup(hass, config):
""" Track states and offer events for rollershutters. """
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_ROLLERSHUTTERS)
component.setup(config)
def handle_rollershutter_service(service):
""" Handles calls to the rollershutter services. """
target_rollershutters = component.extract_from_service(service)
for rollershutter in target_rollershutters:
if service.service == SERVICE_MOVE_UP:
rollershutter.move_up()
elif service.service == SERVICE_MOVE_DOWN:
rollershutter.move_down()
elif service.service == SERVICE_STOP:
rollershutter.stop()
if rollershutter.should_poll:
rollershutter.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_MOVE_UP,
handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_UP))
hass.services.register(DOMAIN, SERVICE_MOVE_DOWN,
handle_rollershutter_service,
descriptions.get(SERVICE_MOVE_DOWN))
hass.services.register(DOMAIN, SERVICE_STOP,
handle_rollershutter_service,
descriptions.get(SERVICE_STOP))
return True
class RollershutterDevice(Entity):
""" Represents a rollershutter within Home Assistant. """
# pylint: disable=no-self-use
@property
def current_position(self):
"""
Return current position of rollershutter.
None is unknown, 0 is closed, 100 is fully open.
"""
raise NotImplementedError()
@property
def state(self):
""" Returns the state of the rollershutter. """
current = self.current_position
if current is None:
return STATE_UNKNOWN
return STATE_CLOSED if current == 0 else STATE_OPEN
@property
def state_attributes(self):
""" Return the state attributes. """
current = self.current_position
if current is None:
return None
return {
ATTR_CURRENT_POSITION: current
}
def move_up(self, **kwargs):
""" Move the rollershutter down. """
raise NotImplementedError()
def move_down(self, **kwargs):
""" Move the rollershutter up. """
raise NotImplementedError()
def stop(self, **kwargs):
""" Stop the rollershutter. """
raise NotImplementedError()

View File

@ -0,0 +1,104 @@
"""
homeassistant.components.rollershutter.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a MQTT rollershutter.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter.mqtt/
"""
import logging
import homeassistant.components.mqtt as mqtt
from homeassistant.components.rollershutter import RollershutterDevice
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
DEFAULT_NAME = "MQTT Rollershutter"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_UP = "UP"
DEFAULT_PAYLOAD_DOWN = "DOWN"
DEFAULT_PAYLOAD_STOP = "STOP"
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add MQTT Rollershutter """
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices_callback([MqttRollershutter(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('payload_up', DEFAULT_PAYLOAD_UP),
config.get('payload_down', DEFAULT_PAYLOAD_DOWN),
config.get('payload_stop', DEFAULT_PAYLOAD_STOP),
config.get('state_format'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttRollershutter(RollershutterDevice):
""" Represents a rollershutter that can be controlled using MQTT. """
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_up, payload_down, payload_stop, state_format):
self._state = None
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_up = payload_up
self._payload_down = payload_down
self._payload_stop = payload_stop
self._parse = mqtt.FmtParser(state_format)
if self._state_topic is None:
return
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
value = self._parse(payload)
if value.isnumeric() and 0 <= int(value) <= 100:
self._state = int(value)
self.update_ha_state()
else:
_LOGGER.warning(
"Payload is expected to be an integer between 0 and 100")
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
""" No polling needed """
return False
@property
def name(self):
""" The name of the rollershutter. """
return self._name
@property
def current_position(self):
"""
Return current position of rollershutter.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._state
def move_up(self, **kwargs):
""" Move the rollershutter up. """
mqtt.publish(self.hass, self._command_topic, self._payload_up,
self._qos)
def move_down(self, **kwargs):
""" Move the rollershutter down. """
mqtt.publish(self.hass, self._command_topic, self._payload_down,
self._qos)
def stop(self, **kwargs):
""" Stop the device. """
mqtt.publish(self.hass, self._command_topic, self._payload_stop,
self._qos)

View File

@ -9,7 +9,6 @@ https://home-assistant.io/components/script/
""" """
import logging import logging
from datetime import timedelta from datetime import timedelta
import homeassistant.util.dt as date_util
from itertools import islice from itertools import islice
import threading import threading
@ -17,6 +16,7 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util import slugify, split_entity_id from homeassistant.util import slugify, split_entity_id
import homeassistant.util.dt as date_util
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, EVENT_TIME_CHANGED, STATE_ON, SERVICE_TURN_ON, ATTR_ENTITY_ID, EVENT_TIME_CHANGED, STATE_ON, SERVICE_TURN_ON,
SERVICE_TURN_OFF) SERVICE_TURN_OFF)
@ -73,11 +73,12 @@ def setup(hass, config):
for object_id, cfg in config[DOMAIN].items(): for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id): if object_id != slugify(object_id):
_LOGGER.warn("Found invalid key for script: %s. Use %s instead.", _LOGGER.warning("Found invalid key for script: %s. Use %s instead",
object_id, slugify(object_id)) object_id, slugify(object_id))
continue continue
if not cfg.get(CONF_SEQUENCE): if not isinstance(cfg.get(CONF_SEQUENCE), list):
_LOGGER.warn("Missing key 'sequence' for script %s", object_id) _LOGGER.warning("Key 'sequence' for script %s should be a list",
object_id)
continue continue
alias = cfg.get(CONF_ALIAS, object_id) alias = cfg.get(CONF_ALIAS, object_id)
script = Script(hass, object_id, alias, cfg[CONF_SEQUENCE]) script = Script(hass, object_id, alias, cfg[CONF_SEQUENCE])

View File

@ -9,10 +9,9 @@ https://home-assistant.io/components/sensor/
import logging import logging
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import wink, zwave, isy994, verisure from homeassistant.components import wink, zwave, isy994, verisure, ecobee
DOMAIN = 'sensor' DOMAIN = 'sensor'
DEPENDENCIES = []
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -22,7 +21,8 @@ DISCOVERY_PLATFORMS = {
wink.DISCOVER_SENSORS: 'wink', wink.DISCOVER_SENSORS: 'wink',
zwave.DISCOVER_SENSORS: 'zwave', zwave.DISCOVER_SENSORS: 'zwave',
isy994.DISCOVER_SENSORS: 'isy994', isy994.DISCOVER_SENSORS: 'isy994',
verisure.DISCOVER_SENSORS: 'verisure' verisure.DISCOVER_SENSORS: 'verisure',
ecobee.DISCOVER_SENSORS: 'ecobee'
} }

View File

@ -6,13 +6,14 @@ The arest sensor will consume an exposed aREST API of a device.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arest/ https://home-assistant.io/components/sensor.arest/
""" """
import logging
import requests
from datetime import timedelta from datetime import timedelta
import logging
import requests
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -0,0 +1,109 @@
"""
homeassistant.components.sensor.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee Thermostat Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.components import ecobee
from homeassistant.const import TEMP_FAHRENHEIT
DEPENDENCIES = ['ecobee']
SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_FAHRENHEIT],
'humidity': ['Humidity', '%'],
'occupancy': ['Occupancy', '']
}
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
if discovery_info is None:
return
data = ecobee.NETWORK
dev = list()
for index in range(len(data.ecobee.thermostats)):
for sensor in data.ecobee.get_remote_sensors(index):
for item in sensor['capability']:
if item['type'] not in ('temperature',
'humidity', 'occupancy'):
continue
dev.append(EcobeeSensor(sensor['name'], item['type'], index))
add_devices(dev)
class EcobeeSensor(Entity):
""" An ecobee sensor. """
def __init__(self, sensor_name, sensor_type, sensor_index):
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
self.sensor_name = sensor_name
self.type = sensor_type
self.index = sensor_index
self._state = None
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
return self._name.rstrip()
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
return self._unit_of_measurement
def update(self):
data = ecobee.NETWORK
data.update()
for sensor in data.ecobee.get_remote_sensors(self.index):
for item in sensor['capability']:
if (
item['type'] == self.type and
self.type == 'temperature' and
self.sensor_name == sensor['name']):
self._state = float(item['value']) / 10
elif (
item['type'] == self.type and
self.type == 'humidity' and
self.sensor_name == sensor['name']):
self._state = item['value']
elif (
item['type'] == self.type and
self.type == 'occupancy' and
self.sensor_name == sensor['name']):
self._state = item['value']

View File

@ -97,5 +97,5 @@ class EfergySensor(Entity):
self._state = response.json()['sum'] self._state = response.json()['sum']
else: else:
self._state = 'Unknown' self._state = 'Unknown'
except RequestException: except (RequestException, ValueError):
_LOGGER.warning('Could not update status for %s', self.name) _LOGGER.warning('Could not update status for %s', self.name)

View File

@ -9,17 +9,11 @@ https://home-assistant.io/components/sensor.forecast/
import logging import logging
from datetime import timedelta from datetime import timedelta
REQUIREMENTS = ['python-forecastio==1.3.3']
try:
import forecastio
except ImportError:
forecastio = None
from homeassistant.util import Throttle from homeassistant.util import Throttle
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS) from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-forecastio==1.3.3']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Sensor types are defined like so: # Sensor types are defined like so:
@ -53,11 +47,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the Forecast.io sensor. """ """ Get the Forecast.io sensor. """
import forecastio
global forecastio # pylint: disable=invalid-name
if forecastio is None:
import forecastio as forecastio_
forecastio = forecastio_
if None in (hass.config.latitude, hass.config.longitude): if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.error("Latitude or longitude not set in Home Assistant config")
@ -141,6 +131,7 @@ class ForeCastSensor(Entity):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def update(self): def update(self):
""" Gets the latest data from Forecast.io and updates the states. """ """ Gets the latest data from Forecast.io and updates the states. """
import forecastio
self.forecast_client.update() self.forecast_client.update()
data = self.forecast_client.data data = self.forecast_client.data
@ -209,6 +200,7 @@ class ForeCastData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
""" Gets the latest data from Forecast.io. """ """ Gets the latest data from Forecast.io. """
import forecastio
forecast = forecastio.load_forecast(self._api_key, forecast = forecastio.load_forecast(self._api_key,
self.latitude, self.latitude,

View File

@ -6,9 +6,10 @@ Gathers system information of hosts which running glances.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.glances/ https://home-assistant.io/components/sensor.glances/
""" """
import logging
import requests
from datetime import timedelta from datetime import timedelta
import logging
import requests
from homeassistant.util import Throttle from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity

View File

@ -31,23 +31,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
config.get('name', DEFAULT_NAME), config.get('name', DEFAULT_NAME),
config.get('state_topic'), config.get('state_topic'),
config.get('qos', DEFAULT_QOS), config.get('qos', DEFAULT_QOS),
config.get('unit_of_measurement'))]) config.get('unit_of_measurement'),
config.get('state_format'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttSensor(Entity): class MqttSensor(Entity):
""" Represents a sensor that can be updated using MQTT. """ """ Represents a sensor that can be updated using MQTT. """
def __init__(self, hass, name, state_topic, qos, unit_of_measurement): def __init__(self, hass, name, state_topic, qos, unit_of_measurement,
state_format):
self._state = "-" self._state = "-"
self._hass = hass self._hass = hass
self._name = name self._name = name
self._state_topic = state_topic self._state_topic = state_topic
self._qos = qos self._qos = qos
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self._parse = mqtt.FmtParser(state_format)
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
""" A new MQTT message has been received. """ """ A new MQTT message has been received. """
self._state = payload self._state = self._parse(payload)
self.update_ha_state() self.update_ha_state()
mqtt.subscribe(hass, self._state_topic, message_received, self._qos) mqtt.subscribe(hass, self._state_topic, message_received, self._qos)

View File

@ -6,10 +6,11 @@ The rest sensor will consume JSON responses sent by an exposed REST API.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rest/ https://home-assistant.io/components/sensor.rest/
""" """
import logging
import requests
from json import loads
from datetime import timedelta from datetime import timedelta
from json import loads
import logging
import requests
from homeassistant.util import Throttle from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity

View File

@ -6,13 +6,10 @@ Allows to configure a binary state sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rpi_gpio/ https://home-assistant.io/components/sensor.rpi_gpio/
""" """
# pylint: disable=import-error
import logging import logging
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
from homeassistant.const import (DEVICE_DEFAULT_NAME, from homeassistant.const import (DEVICE_DEFAULT_NAME,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
@ -29,10 +26,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Raspberry PI GPIO ports. """ """ Sets up the Raspberry PI GPIO ports. """
if GPIO is None: import RPi.GPIO as GPIO
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
return
# pylint: disable=no-member
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
sensors = [] sensors = []
@ -65,6 +59,7 @@ class RPiGPIOSensor(Entity):
def __init__(self, port_name, port_num, pull_mode, def __init__(self, port_name, port_num, pull_mode,
value_high, value_low, bouncetime): value_high, value_low, bouncetime):
# pylint: disable=no-member # pylint: disable=no-member
import RPi.GPIO as GPIO
self._name = port_name or DEVICE_DEFAULT_NAME self._name = port_name or DEVICE_DEFAULT_NAME
self._port = port_num self._port = port_num
self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP

View File

@ -6,12 +6,11 @@ Monitors SABnzbd NZB client API.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.sabnzbd/ https://home-assistant.io/components/sensor.sabnzbd/
""" """
from homeassistant.util import Throttle
from datetime import timedelta from datetime import timedelta
import logging
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import logging
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/' REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip' 'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'

View File

@ -6,14 +6,13 @@ Monitors Transmission BitTorrent client API.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.transmission/ https://home-assistant.io/components/sensor.transmission/
""" """
from homeassistant.util import Throttle
from datetime import timedelta from datetime import timedelta
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers.entity import Entity
import logging import logging
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['transmissionrpc==0.11'] REQUIREMENTS = ['transmissionrpc==0.11']
SENSOR_TYPES = { SENSOR_TYPES = {
'current_status': ['Status', ''], 'current_status': ['Status', ''],

View File

@ -95,7 +95,7 @@ class VeraSensor(Entity):
@property @property
def state_attributes(self): def state_attributes(self):
attr = super().state_attributes attr = {}
if self.vera_device.has_battery: if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'

View File

@ -12,8 +12,8 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'9eb39eaba0717922815e673ad1114c685839d890.zip' '42fdcfa721b1bc583688e3592d8427f4c13ba6d9.zip'
'#python-wink==0.1.1'] '#python-wink==0.2']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -6,9 +6,11 @@ Interfaces with Z-Wave sensors.
For more details about this platform, please refer to the documentation For more details about this platform, please refer to the documentation
at https://home-assistant.io/components/zwave/ at https://home-assistant.io/components/zwave/
""" """
# Because we do not compile openzwave on CI
# pylint: disable=import-error # pylint: disable=import-error
from homeassistant.helpers.event import track_point_in_time
import datetime import datetime
from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.components.zwave as zwave import homeassistant.components.zwave as zwave
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity

View File

@ -12,7 +12,6 @@ import subprocess
from homeassistant.util import slugify from homeassistant.util import slugify
DOMAIN = 'shell_command' DOMAIN = 'shell_command'
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -12,13 +12,14 @@ import urllib
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.event import (
track_point_in_utc_time, track_utc_time_change)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
DEPENDENCIES = []
REQUIREMENTS = ['astral==0.8.1'] REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun" DOMAIN = "sun"
ENTITY_ID = "sun.sun" ENTITY_ID = "sun.sun"
ENTITY_ID_ELEVATION = "sun.elevation"
CONF_ELEVATION = 'elevation' CONF_ELEVATION = 'elevation'
@ -27,6 +28,7 @@ STATE_BELOW_HORIZON = "below_horizon"
STATE_ATTR_NEXT_RISING = "next_rising" STATE_ATTR_NEXT_RISING = "next_rising"
STATE_ATTR_NEXT_SETTING = "next_setting" STATE_ATTR_NEXT_SETTING = "next_setting"
STATE_ATTR_ELEVATION = "elevation"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -140,11 +142,7 @@ class Sun(Entity):
self.hass = hass self.hass = hass
self.location = location self.location = location
self._state = self.next_rising = self.next_setting = None self._state = self.next_rising = self.next_setting = None
track_utc_time_change(hass, self.timer_update, second=30)
@property
def should_poll(self):
""" We trigger updates ourselves after sunset/sunrise """
return False
@property @property
def name(self): def name(self):
@ -160,8 +158,11 @@ class Sun(Entity):
@property @property
def state_attributes(self): def state_attributes(self):
return { return {
STATE_ATTR_NEXT_RISING: dt_util.datetime_to_str(self.next_rising), STATE_ATTR_NEXT_RISING:
STATE_ATTR_NEXT_SETTING: dt_util.datetime_to_str(self.next_setting) dt_util.datetime_to_str(self.next_rising),
STATE_ATTR_NEXT_SETTING:
dt_util.datetime_to_str(self.next_setting),
STATE_ATTR_ELEVATION: round(self.solar_elevation, 2)
} }
@property @property
@ -169,6 +170,15 @@ class Sun(Entity):
""" Returns the datetime when the next change to the state is. """ """ Returns the datetime when the next change to the state is. """
return min(self.next_rising, self.next_setting) return min(self.next_rising, self.next_setting)
@property
def solar_elevation(self):
""" Returns the angle the sun is above the horizon"""
from astral import Astral
return Astral().solar_elevation(
dt_util.utcnow(),
self.location.latitude,
self.location.longitude)
def update_as_of(self, utc_point_in_time): def update_as_of(self, utc_point_in_time):
""" Calculate sun state at a point in UTC time. """ """ Calculate sun state at a point in UTC time. """
mod = -1 mod = -1
@ -199,3 +209,7 @@ class Sun(Entity):
track_point_in_utc_time( track_point_in_utc_time(
self.hass, self.point_in_time_listener, self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1)) self.next_change + timedelta(seconds=1))
def timer_update(self, time):
""" Needed to update solar elevation. """
self.update_ha_state()

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