mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
fix req_all
This commit is contained in:
commit
02a8ce71d0
@ -32,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
|
||||||
@ -51,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
|
||||||
@ -87,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
|
||||||
@ -101,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
|
||||||
|
@ -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:
|
||||||
|
12
Dockerfile
12
Dockerfile
@ -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" ]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
include README.md
|
include README.rst
|
||||||
include LICENSE
|
include LICENSE
|
||||||
graft homeassistant
|
graft homeassistant
|
||||||
prune homeassistant/components/frontend/www_static/home-assistant-polymer
|
prune homeassistant/components/frontend/www_static/home-assistant-polymer
|
||||||
|
@ -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 '
|
||||||
|
@ -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 + '.{}'
|
||||||
|
13
homeassistant/components/alarm_control_panel/demo.py
Normal file
13
homeassistant/components/alarm_control_panel/demo.py
Normal 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),
|
||||||
|
])
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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__)
|
||||||
|
@ -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),
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
49
homeassistant/components/binary_sensor/__init__.py
Normal file
49
homeassistant/components/binary_sensor/__init__.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.binary_sensor
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Component to interface with binary sensors (sensors which only know two states)
|
||||||
|
that can be monitored.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/binary_sensor/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
|
DOMAIN = 'binary_sensor'
|
||||||
|
SCAN_INTERVAL = 30
|
||||||
|
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Track states and offer events for binary sensors. """
|
||||||
|
component = EntityComponent(
|
||||||
|
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
||||||
|
|
||||||
|
component.setup(config)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
class BinarySensorDevice(Entity):
|
||||||
|
""" Represents a binary sensor. """
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if the binary sensor is on. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the binary sensor. """
|
||||||
|
return STATE_ON if self.is_on else STATE_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def friendly_state(self):
|
||||||
|
""" Returns the friendly state of the binary sensor. """
|
||||||
|
return None
|
107
homeassistant/components/binary_sensor/arest.py
Normal file
107
homeassistant/components/binary_sensor/arest.py
Normal 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)
|
37
homeassistant/components/binary_sensor/demo.py
Normal file
37
homeassistant/components/binary_sensor/demo.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.binary_sensor.demo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Demo platform that has two fake binary sensors.
|
||||||
|
"""
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Demo binary sensors. """
|
||||||
|
add_devices([
|
||||||
|
DemoBinarySensor('Basement Floor Wet', False),
|
||||||
|
DemoBinarySensor('Movement Backyard', True),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class DemoBinarySensor(BinarySensorDevice):
|
||||||
|
""" A Demo binary sensor. """
|
||||||
|
|
||||||
|
def __init__(self, name, state):
|
||||||
|
self._name = name
|
||||||
|
self._state = state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed for a demo binary sensor. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the binary sensor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if the binary sensor is on. """
|
||||||
|
return self._state
|
76
homeassistant/components/binary_sensor/mqtt.py
Normal file
76
homeassistant/components/binary_sensor/mqtt.py
Normal 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
|
@ -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"
|
||||||
|
|
||||||
|
@ -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']
|
||||||
@ -58,7 +59,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n'
|
|||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Track states and offer events for sensors. """
|
""" Track states and offer events for cameras. """
|
||||||
|
|
||||||
component = EntityComponent(
|
component = EntityComponent(
|
||||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
|
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
|
||||||
@ -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',
|
||||||
@ -101,18 +104,16 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def _proxy_camera_mjpeg_stream(handler, path_match, data):
|
def _proxy_camera_mjpeg_stream(handler, path_match, data):
|
||||||
""" Proxies the camera image as an mjpeg stream via the HA server.
|
"""
|
||||||
|
Proxies the camera image as an mjpeg stream via the HA server.
|
||||||
This function takes still images from the IP camera and turns them
|
This function takes still images from the IP camera and turns them
|
||||||
into an MJPEG stream. This means that HA can return a live video
|
into an MJPEG stream. This means that HA can return a live video
|
||||||
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
|
||||||
@ -130,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
|
||||||
@ -147,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(
|
||||||
@ -163,7 +163,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
|
|
||||||
class Camera(Entity):
|
class Camera(Entity):
|
||||||
""" The base class for camera components """
|
""" The base class for camera components. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.is_streaming = False
|
self.is_streaming = False
|
||||||
@ -171,23 +171,23 @@ class Camera(Entity):
|
|||||||
@property
|
@property
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
def is_recording(self):
|
def is_recording(self):
|
||||||
""" Returns true if the device is recording """
|
""" Returns true if the device is recording. """
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
def brand(self):
|
def brand(self):
|
||||||
""" Should return a string of the camera brand """
|
""" Should return a string of the camera brand. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
def model(self):
|
def model(self):
|
||||||
""" Returns string of camera model """
|
""" Returns string of camera model. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def camera_image(self):
|
def camera_image(self):
|
||||||
""" Return bytes of camera image """
|
""" Return bytes of camera image. """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
37
homeassistant/components/camera/demo.py
Normal file
37
homeassistant/components/camera/demo.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.camera.demo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Demo platform that has a fake camera.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from homeassistant.components.camera import Camera
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Demo camera. """
|
||||||
|
add_devices([
|
||||||
|
DemoCamera('Demo camera')
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class DemoCamera(Camera):
|
||||||
|
""" A Demo camera. """
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
super().__init__()
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def camera_image(self):
|
||||||
|
""" Return a faked still image response. """
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
image_path = os.path.join(os.path.dirname(__file__),
|
||||||
|
'demo_{}.jpg'.format(now.second % 4))
|
||||||
|
with open(image_path, 'rb') as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Return the name of this device. """
|
||||||
|
return self._name
|
BIN
homeassistant/components/camera/demo_0.jpg
Normal file
BIN
homeassistant/components/camera/demo_0.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
homeassistant/components/camera/demo_1.jpg
Normal file
BIN
homeassistant/components/camera/demo_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
homeassistant/components/camera/demo_2.jpg
Normal file
BIN
homeassistant/components/camera/demo_2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
homeassistant/components/camera/demo_3.jpg
Normal file
BIN
homeassistant/components/camera/demo_3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
@ -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__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class GenericCamera(Camera):
|
|||||||
self._still_image_url = device_info['still_image_url']
|
self._still_image_url = device_info['still_image_url']
|
||||||
|
|
||||||
def camera_image(self):
|
def camera_image(self):
|
||||||
""" Return a still image reponse from the camera. """
|
""" Return a still image response from the camera. """
|
||||||
if self._username and self._password:
|
if self._username and self._password:
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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']
|
'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',
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -26,19 +26,19 @@ ecobee:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY)
|
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY)
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
DOMAIN = "ecobee"
|
DOMAIN = "ecobee"
|
||||||
DISCOVER_THERMOSTAT = "ecobee.thermostat"
|
DISCOVER_THERMOSTAT = "ecobee.thermostat"
|
||||||
DISCOVER_SENSORS = "ecobee.sensor"
|
DISCOVER_SENSORS = "ecobee.sensor"
|
||||||
DEPENDENCIES = []
|
|
||||||
NETWORK = None
|
NETWORK = None
|
||||||
HOLD_TEMP = 'hold_temp'
|
HOLD_TEMP = 'hold_temp'
|
||||||
|
|
||||||
@ -93,8 +93,8 @@ def setup_ecobee(hass, network, config):
|
|||||||
configurator.request_done(_CONFIGURING.pop('ecobee'))
|
configurator.request_done(_CONFIGURING.pop('ecobee'))
|
||||||
|
|
||||||
# Ensure component is loaded
|
# Ensure component is loaded
|
||||||
bootstrap.setup_component(hass, 'thermostat')
|
bootstrap.setup_component(hass, 'thermostat', config)
|
||||||
bootstrap.setup_component(hass, 'sensor')
|
bootstrap.setup_component(hass, 'sensor', config)
|
||||||
|
|
||||||
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
|
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "b75e3c9ebd3de2dae0912a89499127a9"
|
VERSION = "ece94598f1575599c5aefa7322b1423b"
|
||||||
|
BIN
homeassistant/components/frontend/www_static/favicon-384x384.png
Normal file
BIN
homeassistant/components/frontend/www_static/favicon-384x384.png
Normal file
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 99af263595dbbf057d26bb266101fa1e386442c6
|
Subproject commit c130933f45262dd1c7b4fd75d23a90c132fb271f
|
@ -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
@ -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 + ".{}"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
|
|
||||||
DOMAIN = "keyboard"
|
DOMAIN = "keyboard"
|
||||||
DEPENDENCIES = []
|
|
||||||
REQUIREMENTS = ['pyuserinput==0.1.9']
|
REQUIREMENTS = ['pyuserinput==0.1.9']
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -8,20 +8,19 @@ https://home-assistant.io/components/light.blinksticklight/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from blinkstick import blinkstick
|
from homeassistant.components.light import Light, ATTR_RGB_COLOR
|
||||||
|
|
||||||
from homeassistant.components.light import (Light, ATTR_RGB_COLOR)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ["blinkstick==1.1.7"]
|
REQUIREMENTS = ["blinkstick==1.1.7"]
|
||||||
DEPENDENCIES = []
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Add device specified by serial number. """
|
""" Add device specified by serial number. """
|
||||||
|
from blinkstick import blinkstick
|
||||||
|
|
||||||
stick = blinkstick.find_by_serial(config['serial'])
|
stick = blinkstick.find_by_serial(config['serial'])
|
||||||
|
|
||||||
add_devices_callback([BlinkStickLight(stick, config['name'])])
|
add_devices_callback([BlinkStickLight(stick, config['name'])])
|
||||||
|
@ -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]
|
||||||
|
@ -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])
|
||||||
|
@ -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):
|
||||||
|
@ -8,11 +8,15 @@ https://home-assistant.io/components/light.rfxtrx/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import homeassistant.components.rfxtrx as rfxtrx
|
import homeassistant.components.rfxtrx as rfxtrx
|
||||||
import RFXtrx as rfxtrxmod
|
|
||||||
|
|
||||||
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__)
|
||||||
@ -20,14 +24,24 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Setup the RFXtrx platform. """
|
""" Setup the RFXtrx platform. """
|
||||||
|
import RFXtrx as rfxtrxmod
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -53,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,
|
||||||
@ -66,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:
|
||||||
@ -78,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):
|
||||||
@ -93,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. """
|
||||||
|
@ -6,35 +6,30 @@ Support for Tellstick 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.tellstick/
|
https://home-assistant.io/components/light.tellstick/
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
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)
|
||||||
import tellcore.constants as tellcore_constants
|
|
||||||
from tellcore.library import DirectCallbackDispatcher
|
|
||||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||||
|
SIGNAL_REPETITIONS = 1
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Find and return Tellstick lights. """
|
""" Find and return Tellstick lights. """
|
||||||
|
|
||||||
try:
|
|
||||||
import tellcore.telldus as telldus
|
import tellcore.telldus as telldus
|
||||||
except ImportError:
|
from tellcore.library import DirectCallbackDispatcher
|
||||||
logging.getLogger(__name__).exception(
|
import tellcore.constants as tellcore_constants
|
||||||
"Failed to import tellcore")
|
|
||||||
return []
|
|
||||||
|
|
||||||
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 """
|
||||||
@ -58,16 +53,21 @@ 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. """
|
||||||
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
|
||||||
|
def __init__(self, tellstick_device, signal_repetitions):
|
||||||
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
|
self.tellstick_device = tellstick_device
|
||||||
|
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
|
||||||
|
self.signal_repetitions = signal_repetitions
|
||||||
|
self._brightness = 0
|
||||||
|
|
||||||
|
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
||||||
tellcore_constants.TELLSTICK_TURNOFF |
|
tellcore_constants.TELLSTICK_TURNOFF |
|
||||||
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()
|
||||||
def __init__(self, tellstick_device):
|
|
||||||
self.tellstick_device = tellstick_device
|
|
||||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
|
|
||||||
self._brightness = 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -86,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()
|
||||||
@ -99,11 +100,14 @@ 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()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Update state of the light. """
|
""" Update state of the light. """
|
||||||
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
last_command = self.tellstick_device.last_sent_command(
|
last_command = self.tellstick_device.last_sent_command(
|
||||||
self.last_sent_command_mask)
|
self.last_sent_command_mask)
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -6,15 +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
|
||||||
from openzwave.network import ZWaveNetwork
|
from threading import Timer
|
||||||
from pydispatch import dispatcher
|
|
||||||
|
|
||||||
import homeassistant.components.zwave as zwave
|
|
||||||
|
|
||||||
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):
|
||||||
@ -51,6 +49,9 @@ class ZwaveDimmer(Light):
|
|||||||
""" Provides a Z-Wave dimmer. """
|
""" Provides a Z-Wave dimmer. """
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
|
from openzwave.network import ZWaveNetwork
|
||||||
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
self._value = value
|
self._value = value
|
||||||
self._node = value.node
|
self._node = value.node
|
||||||
|
|
||||||
|
112
homeassistant/components/lock/__init__.py
Normal file
112
homeassistant/components/lock/__init__.py
Normal 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
|
49
homeassistant/components/lock/demo.py
Normal file
49
homeassistant/components/lock/demo.py
Normal 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()
|
0
homeassistant/components/lock/services.yaml
Normal file
0
homeassistant/components/lock/services.yaml
Normal file
68
homeassistant/components/lock/wink.py
Normal file
68
homeassistant/components/lock/wink.py
Normal 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)
|
@ -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,
|
||||||
|
@ -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 + '.{}'
|
||||||
|
@ -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)
|
||||||
|
@ -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 | \
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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']
|
||||||
|
|
||||||
|
143
homeassistant/components/motor/__init__.py
Normal file
143
homeassistant/components/motor/__init__.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.motor
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Motor component.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/motor/
|
||||||
|
"""
|
||||||
|
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_OPEN, SERVICE_CLOSE, SERVICE_STOP,
|
||||||
|
STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID)
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = 'motor'
|
||||||
|
SCAN_INTERVAL = 15
|
||||||
|
|
||||||
|
GROUP_NAME_ALL_MOTORS = 'all motors'
|
||||||
|
ENTITY_ID_ALL_MOTORS = group.ENTITY_ID_FORMAT.format('all_motors')
|
||||||
|
|
||||||
|
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 motor is open based on the statemachine. """
|
||||||
|
entity_id = entity_id or ENTITY_ID_ALL_MOTORS
|
||||||
|
return hass.states.is_state(entity_id, STATE_OPEN)
|
||||||
|
|
||||||
|
|
||||||
|
def call_open(hass, entity_id=None):
|
||||||
|
""" Open all or specified motor. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
hass.services.call(DOMAIN, SERVICE_OPEN, data)
|
||||||
|
|
||||||
|
|
||||||
|
def call_close(hass, entity_id=None):
|
||||||
|
""" Close all or specified motor. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
|
||||||
|
|
||||||
|
|
||||||
|
def call_stop(hass, entity_id=None):
|
||||||
|
""" Stops all or specified motor. """
|
||||||
|
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 motors. """
|
||||||
|
component = EntityComponent(
|
||||||
|
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
|
||||||
|
GROUP_NAME_ALL_MOTORS)
|
||||||
|
component.setup(config)
|
||||||
|
|
||||||
|
def handle_motor_service(service):
|
||||||
|
""" Handles calls to the motor services. """
|
||||||
|
target_motors = component.extract_from_service(service)
|
||||||
|
|
||||||
|
for motor in target_motors:
|
||||||
|
if service.service == SERVICE_OPEN:
|
||||||
|
motor.open()
|
||||||
|
elif service.service == SERVICE_CLOSE:
|
||||||
|
motor.close()
|
||||||
|
elif service.service == SERVICE_STOP:
|
||||||
|
motor.stop()
|
||||||
|
|
||||||
|
if motor.should_poll:
|
||||||
|
motor.update_ha_state(True)
|
||||||
|
|
||||||
|
descriptions = load_yaml_config_file(
|
||||||
|
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||||
|
|
||||||
|
hass.services.register(DOMAIN, SERVICE_OPEN,
|
||||||
|
handle_motor_service,
|
||||||
|
descriptions.get(SERVICE_OPEN))
|
||||||
|
hass.services.register(DOMAIN, SERVICE_CLOSE,
|
||||||
|
handle_motor_service,
|
||||||
|
descriptions.get(SERVICE_CLOSE))
|
||||||
|
hass.services.register(DOMAIN, SERVICE_STOP,
|
||||||
|
handle_motor_service,
|
||||||
|
descriptions.get(SERVICE_STOP))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MotorDevice(Entity):
|
||||||
|
""" Represents a motor within Home Assistant. """
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_position(self):
|
||||||
|
"""
|
||||||
|
Return current position of motor.
|
||||||
|
None is unknown, 0 is closed, 100 is fully open.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the motor. """
|
||||||
|
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 open(self, **kwargs):
|
||||||
|
""" Open the motor. """
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def close(self, **kwargs):
|
||||||
|
""" Close the motor. """
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def stop(self, **kwargs):
|
||||||
|
""" Stop the motor. """
|
||||||
|
raise NotImplementedError()
|
104
homeassistant/components/motor/mqtt.py
Normal file
104
homeassistant/components/motor/mqtt.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.motor.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a MQTT motor.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/motor.mqtt/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
from homeassistant.components.motor import MotorDevice
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
DEFAULT_NAME = "MQTT Motor"
|
||||||
|
DEFAULT_QOS = 0
|
||||||
|
DEFAULT_PAYLOAD_OPEN = "OPEN"
|
||||||
|
DEFAULT_PAYLOAD_CLOSE = "CLOSE"
|
||||||
|
DEFAULT_PAYLOAD_STOP = "STOP"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Add MQTT Motor """
|
||||||
|
|
||||||
|
if config.get('command_topic') is None:
|
||||||
|
_LOGGER.error("Missing required variable: command_topic")
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices_callback([MqttMotor(
|
||||||
|
hass,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('state_topic'),
|
||||||
|
config.get('command_topic'),
|
||||||
|
config.get('qos', DEFAULT_QOS),
|
||||||
|
config.get('payload_open', DEFAULT_PAYLOAD_OPEN),
|
||||||
|
config.get('payload_close', DEFAULT_PAYLOAD_CLOSE),
|
||||||
|
config.get('payload_stop', DEFAULT_PAYLOAD_STOP),
|
||||||
|
config.get('state_format'))])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
class MqttMotor(MotorDevice):
|
||||||
|
""" Represents a motor that can be controlled using MQTT. """
|
||||||
|
def __init__(self, hass, name, state_topic, command_topic, qos,
|
||||||
|
payload_open, payload_close, 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_open = payload_open
|
||||||
|
self._payload_close = payload_close
|
||||||
|
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 motor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_position(self):
|
||||||
|
"""
|
||||||
|
Return current position of motor.
|
||||||
|
None is unknown, 0 is closed, 100 is fully open.
|
||||||
|
"""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def open(self, **kwargs):
|
||||||
|
""" Open the device. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_open,
|
||||||
|
self._qos)
|
||||||
|
|
||||||
|
def close(self, **kwargs):
|
||||||
|
""" Close the device. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_close,
|
||||||
|
self._qos)
|
||||||
|
|
||||||
|
def stop(self, **kwargs):
|
||||||
|
""" Stop the device. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_stop,
|
||||||
|
self._qos)
|
0
homeassistant/components/motor/services.yaml
Normal file
0
homeassistant/components/motor/services.yaml
Normal 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))
|
||||||
|
@ -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"
|
||||||
|
@ -47,21 +47,23 @@ class PushBulletNotificationService(BaseNotificationService):
|
|||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
'''
|
"""
|
||||||
Refresh devices, contacts, channels, 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
|
||||||
|
implemented in the module yet.
|
||||||
|
"""
|
||||||
self.pushbullet.refresh()
|
self.pushbullet.refresh()
|
||||||
self.pbtargets = {
|
self.pbtargets = {
|
||||||
'device':
|
'device': {
|
||||||
{tgt.nickname: tgt for tgt in self.pushbullet.devices},
|
tgt.nickname.lower(): tgt for tgt in self.pushbullet.devices},
|
||||||
'contact':
|
'channel': {
|
||||||
{tgt.email: tgt for tgt in self.pushbullet.contacts},
|
tgt.channel_tag.lower(): tgt for
|
||||||
'channel':
|
tgt in self.pushbullet.channels},
|
||||||
{tgt.channel_tag: tgt for tgt in self.pushbullet.channels},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def send_message(self, message=None, **kwargs):
|
def send_message(self, message=None, **kwargs):
|
||||||
@ -69,6 +71,8 @@ class PushBulletNotificationService(BaseNotificationService):
|
|||||||
Send a message to a specified target.
|
Send a message to a specified target.
|
||||||
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
|
||||||
|
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)
|
||||||
@ -86,24 +90,27 @@ class PushBulletNotificationService(BaseNotificationService):
|
|||||||
|
|
||||||
# Main loop, Process all targets specified
|
# Main loop, Process all targets specified
|
||||||
for target in targets:
|
for target in targets:
|
||||||
|
|
||||||
# Allow for untargeted push, combined with other types
|
|
||||||
if target in ['device', 'device/']:
|
|
||||||
self.pushbullet.push_note(title, message)
|
|
||||||
_LOGGER.info('Sent notification to self')
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ttype, tname = target.split('/', 1)
|
ttype, tname = target.split('/', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.error('Invalid target syntax: %s', target)
|
_LOGGER.error('Invalid target syntax: %s', target)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Target is email, send directly, don't use a target object
|
||||||
|
# This also seems works to send to all devices in own account
|
||||||
|
if ttype == 'email':
|
||||||
|
self.pushbullet.push_note(title, message, email=tname)
|
||||||
|
_LOGGER.info('Sent notification to email %s', tname)
|
||||||
|
continue
|
||||||
|
|
||||||
# Refresh if name not found. While awaiting periodic refresh
|
# Refresh if name not found. While awaiting periodic refresh
|
||||||
# solution in component, poor mans refresh ;)
|
# solution in component, poor mans refresh ;)
|
||||||
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
|
||||||
|
|
||||||
|
tname = tname.lower()
|
||||||
|
|
||||||
if tname not in self.pbtargets[ttype] and not refreshed:
|
if tname not in self.pbtargets[ttype] and not refreshed:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
refreshed = True
|
refreshed = True
|
||||||
@ -112,10 +119,10 @@ class PushBulletNotificationService(BaseNotificationService):
|
|||||||
# name. Dict pbtargets has all *actual* targets.
|
# name. Dict pbtargets has all *actual* targets.
|
||||||
try:
|
try:
|
||||||
self.pbtargets[ttype][tname].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)
|
|
||||||
|
@ -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. """
|
||||||
|
@ -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.")
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
@ -12,7 +12,6 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
from homeassistant.components import wink, zwave, isy994, verisure, ecobee
|
from homeassistant.components import wink, zwave, isy994, verisure, ecobee
|
||||||
|
|
||||||
DOMAIN = 'sensor'
|
DOMAIN = 'sensor'
|
||||||
DEPENDENCIES = []
|
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
|
@ -25,10 +25,11 @@ ecobee:
|
|||||||
hold_temp: True
|
hold_temp: True
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components import ecobee
|
from homeassistant.components import ecobee
|
||||||
from homeassistant.const import TEMP_FAHRENHEIT
|
from homeassistant.const import TEMP_FAHRENHEIT
|
||||||
import logging
|
|
||||||
|
|
||||||
DEPENDENCIES = ['ecobee']
|
DEPENDENCIES = ['ecobee']
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
@ -116,7 +117,11 @@ class GlancesSensor(Entity):
|
|||||||
elif self.type == 'disk_use':
|
elif self.type == 'disk_use':
|
||||||
return round(value['fs'][0]['used'] / 1024**3, 1)
|
return round(value['fs'][0]['used'] / 1024**3, 1)
|
||||||
elif self.type == 'disk_free':
|
elif self.type == 'disk_free':
|
||||||
|
try:
|
||||||
return round(value['fs'][0]['free'] / 1024**3, 1)
|
return round(value['fs'][0]['free'] / 1024**3, 1)
|
||||||
|
except KeyError:
|
||||||
|
return round((value['fs'][0]['size'] -
|
||||||
|
value['fs'][0]['used']) / 1024**3, 1)
|
||||||
elif self.type == 'memory_use_percent':
|
elif self.type == 'memory_use_percent':
|
||||||
return value['mem']['percent']
|
return value['mem']['percent']
|
||||||
elif self.type == 'memory_use':
|
elif self.type == 'memory_use':
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -11,7 +11,6 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
from homeassistant.const import (TEMP_CELCIUS)
|
from homeassistant.const import (TEMP_CELCIUS)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from RFXtrx import SensorEvent
|
|
||||||
import homeassistant.components.rfxtrx as rfxtrx
|
import homeassistant.components.rfxtrx as rfxtrx
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
@ -28,6 +27,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Setup the RFXtrx platform. """
|
""" Setup the RFXtrx platform. """
|
||||||
|
from RFXtrx import SensorEvent
|
||||||
|
|
||||||
def sensor_update(event):
|
def sensor_update(event):
|
||||||
""" Callback for sensor updates from the RFXtrx gateway. """
|
""" Callback for sensor updates from the RFXtrx gateway. """
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -9,9 +9,6 @@ https://home-assistant.io/components/sensor.tellstick/
|
|||||||
import logging
|
import logging
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import tellcore.telldus as telldus
|
|
||||||
import tellcore.constants as tellcore_constants
|
|
||||||
|
|
||||||
from homeassistant.const import TEMP_CELCIUS
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
@ -24,6 +21,9 @@ REQUIREMENTS = ['tellcore-py==1.1.2']
|
|||||||
# 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 Tellstick sensors. """
|
""" Sets up Tellstick sensors. """
|
||||||
|
import tellcore.telldus as telldus
|
||||||
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
sensor_value_descriptions = {
|
sensor_value_descriptions = {
|
||||||
tellcore_constants.TELLSTICK_TEMPERATURE:
|
tellcore_constants.TELLSTICK_TEMPERATURE:
|
||||||
DatatypeDescription(
|
DatatypeDescription(
|
||||||
|
@ -6,18 +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
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
import transmissionrpc
|
|
||||||
|
|
||||||
from transmissionrpc.error import TransmissionError
|
|
||||||
|
|
||||||
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', ''],
|
||||||
@ -33,6 +28,9 @@ _THROTTLED_REFRESH = None
|
|||||||
# 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 Transmission sensors. """
|
""" Sets up the Transmission sensors. """
|
||||||
|
import transmissionrpc
|
||||||
|
from transmissionrpc.error import TransmissionError
|
||||||
|
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
username = config.get(CONF_USERNAME, None)
|
username = config.get(CONF_USERNAME, None)
|
||||||
password = config.get(CONF_PASSWORD, None)
|
password = config.get(CONF_PASSWORD, None)
|
||||||
@ -97,6 +95,8 @@ class TransmissionSensor(Entity):
|
|||||||
|
|
||||||
def refresh_transmission_data(self):
|
def refresh_transmission_data(self):
|
||||||
""" Calls the throttled Transmission refresh method. """
|
""" Calls the throttled Transmission refresh method. """
|
||||||
|
from transmissionrpc.error import TransmissionError
|
||||||
|
|
||||||
if _THROTTLED_REFRESH is not None:
|
if _THROTTLED_REFRESH is not None:
|
||||||
try:
|
try:
|
||||||
_THROTTLED_REFRESH()
|
_THROTTLED_REFRESH()
|
||||||
|
@ -15,9 +15,9 @@ from homeassistant.const import (
|
|||||||
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/balloob/home-assistant-vera-api/archive/'
|
REQUIREMENTS = ['https://github.com/pavoni/home-assistant-vera-api/archive/'
|
||||||
'a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip'
|
'efdba4e63d58a30bc9b36d9e01e69858af9130b8.zip'
|
||||||
'#python-vera==0.1']
|
'#python-vera==0.1.1']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -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 + '%'
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -6,11 +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
|
|
||||||
from openzwave.network import ZWaveNetwork
|
|
||||||
from pydispatch import dispatcher
|
|
||||||
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
|
||||||
@ -79,6 +79,9 @@ class ZWaveSensor(Entity):
|
|||||||
""" Represents a Z-Wave sensor. """
|
""" Represents a Z-Wave sensor. """
|
||||||
|
|
||||||
def __init__(self, sensor_value):
|
def __init__(self, sensor_value):
|
||||||
|
from openzwave.network import ZWaveNetwork
|
||||||
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
self._value = sensor_value
|
self._value = sensor_value
|
||||||
self._node = sensor_value.node
|
self._node = sensor_value.node
|
||||||
|
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -20,7 +20,6 @@ from homeassistant.components import (
|
|||||||
group, discovery, wink, isy994, verisure, zwave)
|
group, discovery, wink, isy994, verisure, zwave)
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
DEPENDENCIES = []
|
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
|
|
||||||
GROUP_NAME_ALL_SWITCHES = 'all switches'
|
GROUP_NAME_ALL_SWITCHES = 'all switches'
|
||||||
|
@ -34,30 +34,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
dev = []
|
dev = []
|
||||||
pins = config.get('pins')
|
pins = config.get('pins', {})
|
||||||
for pinnum, pin in pins.items():
|
for pinnum, pin in pins.items():
|
||||||
dev.append(ArestSwitch(resource,
|
dev.append(ArestSwitchPin(resource,
|
||||||
config.get('name', response.json()['name']),
|
config.get('name', response.json()['name']),
|
||||||
pin.get('name'),
|
pin.get('name'),
|
||||||
pinnum))
|
pinnum))
|
||||||
|
|
||||||
|
functions = config.get('functions', {})
|
||||||
|
for funcname, func in functions.items():
|
||||||
|
dev.append(ArestSwitchFunction(resource,
|
||||||
|
config.get('name',
|
||||||
|
response.json()['name']),
|
||||||
|
func.get('name'),
|
||||||
|
funcname))
|
||||||
|
|
||||||
add_devices(dev)
|
add_devices(dev)
|
||||||
|
|
||||||
|
|
||||||
class ArestSwitch(SwitchDevice):
|
class ArestSwitchBase(SwitchDevice):
|
||||||
""" Implements an aREST switch. """
|
""" Implements an aREST switch. """
|
||||||
|
|
||||||
def __init__(self, resource, location, name, pin):
|
def __init__(self, resource, location, name):
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
self._name = '{} {}'.format(location.title(), name.title()) \
|
self._name = '{} {}'.format(location.title(), name.title()) \
|
||||||
or DEVICE_DEFAULT_NAME
|
or DEVICE_DEFAULT_NAME
|
||||||
self._pin = pin
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
request = requests.get('{}/mode/{}/o'.format(self._resource,
|
|
||||||
self._pin), timeout=10)
|
|
||||||
if request.status_code is not 200:
|
|
||||||
_LOGGER.error("Can't set mode. Is device offline?")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" The name of the switch. """
|
""" The name of the switch. """
|
||||||
@ -68,6 +71,72 @@ class ArestSwitch(SwitchDevice):
|
|||||||
""" True if device is on. """
|
""" True if device is on. """
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
|
||||||
|
class ArestSwitchFunction(ArestSwitchBase):
|
||||||
|
""" Implements an aREST switch. Based on functions. """
|
||||||
|
|
||||||
|
def __init__(self, resource, location, name, func):
|
||||||
|
super().__init__(resource, location, name)
|
||||||
|
self._func = func
|
||||||
|
|
||||||
|
request = requests.get('{}/{}'.format(self._resource, self._func),
|
||||||
|
timeout=10)
|
||||||
|
|
||||||
|
if request.status_code is not 200:
|
||||||
|
_LOGGER.error("Can't find function. Is device offline?")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
request.json()['return_value']
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.error("No return_value received. "
|
||||||
|
"Is the function name correct.")
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.error("Response invalid. Is the function name correct.")
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
request = requests.get('{}/{}'.format(self._resource, self._func),
|
||||||
|
timeout=10, params={"params": "1"})
|
||||||
|
|
||||||
|
if request.status_code == 200:
|
||||||
|
self._state = True
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Can't turn on function %s at %s. "
|
||||||
|
"Is device offline?",
|
||||||
|
self._func, self._resource)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
request = requests.get('{}/{}'.format(self._resource, self._func),
|
||||||
|
timeout=10, params={"params": "0"})
|
||||||
|
|
||||||
|
if request.status_code == 200:
|
||||||
|
self._state = False
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Can't turn off function %s at %s. "
|
||||||
|
"Is device offline?",
|
||||||
|
self._func, self._resource)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data from aREST API and updates the state. """
|
||||||
|
request = requests.get('{}/{}'.format(self._resource,
|
||||||
|
self._func), timeout=10)
|
||||||
|
self._state = request.json()['return_value'] != 0
|
||||||
|
|
||||||
|
|
||||||
|
class ArestSwitchPin(ArestSwitchBase):
|
||||||
|
""" Implements an aREST switch. Based on digital I/O """
|
||||||
|
|
||||||
|
def __init__(self, resource, location, name, pin):
|
||||||
|
super().__init__(resource, location, name)
|
||||||
|
self._pin = pin
|
||||||
|
|
||||||
|
request = requests.get('{}/mode/{}/o'.format(self._resource,
|
||||||
|
self._pin), timeout=10)
|
||||||
|
if request.status_code is not 200:
|
||||||
|
_LOGGER.error("Can't set mode. Is device offline?")
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turn the device on. """
|
""" Turn the device on. """
|
||||||
request = requests.get('{}/digital/{}/1'.format(self._resource,
|
request = requests.get('{}/digital/{}/1'.format(self._resource,
|
||||||
@ -76,7 +145,7 @@ class ArestSwitch(SwitchDevice):
|
|||||||
self._state = True
|
self._state = True
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Can't turn on pin %s at %s. Is device offline?",
|
_LOGGER.error("Can't turn on pin %s at %s. Is device offline?",
|
||||||
self._resource, self._pin)
|
self._pin, self._resource)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" Turn the device off. """
|
""" Turn the device off. """
|
||||||
@ -86,7 +155,7 @@ class ArestSwitch(SwitchDevice):
|
|||||||
self._state = False
|
self._state = False
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Can't turn off pin %s at %s. Is device offline?",
|
_LOGGER.error("Can't turn off pin %s at %s. Is device offline?",
|
||||||
self._resource, self._pin)
|
self._pin, self._resource)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest data from aREST API and updates the state. """
|
""" Gets the latest data from aREST API and updates the state. """
|
||||||
|
@ -6,16 +6,11 @@ Support turning on/off motion detection on Hikvision 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/switch.hikvision/
|
https://home-assistant.io/components/switch.hikvision/
|
||||||
"""
|
"""
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
import hikvision.api
|
from homeassistant.const import (STATE_ON, STATE_OFF,
|
||||||
from hikvision.error import HikvisionError, MissingParamError
|
CONF_HOST, CONF_USERNAME, CONF_PASSWORD)
|
||||||
except ImportError:
|
|
||||||
hikvision.api = None
|
|
||||||
|
|
||||||
_LOGGING = logging.getLogger(__name__)
|
_LOGGING = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['hikvision==0.4']
|
REQUIREMENTS = ['hikvision==0.4']
|
||||||
@ -25,6 +20,8 @@ REQUIREMENTS = ['hikvision==0.4']
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
""" Setup Hikvision camera. """
|
""" Setup Hikvision camera. """
|
||||||
|
import hikvision.api
|
||||||
|
from hikvision.error import HikvisionError, MissingParamError
|
||||||
|
|
||||||
host = config.get(CONF_HOST, None)
|
host = config.get(CONF_HOST, None)
|
||||||
port = config.get('port', "80")
|
port = config.get('port', "80")
|
||||||
@ -32,13 +29,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
username = config.get(CONF_USERNAME, "admin")
|
username = config.get(CONF_USERNAME, "admin")
|
||||||
password = config.get(CONF_PASSWORD, "12345")
|
password = config.get(CONF_PASSWORD, "12345")
|
||||||
|
|
||||||
if hikvision.api is None:
|
|
||||||
_LOGGING.error((
|
|
||||||
"Failed to import hikvision. Did you maybe not install the "
|
|
||||||
"'hikvision' dependency?"))
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hikvision_cam = hikvision.api.CreateDevice(
|
hikvision_cam = hikvision.api.CreateDevice(
|
||||||
host, port=port, username=username,
|
host, port=port, username=username,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user