mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
commit
d0bcec12b9
16
.coveragerc
16
.coveragerc
@ -10,6 +10,9 @@ omit =
|
|||||||
homeassistant/components/arduino.py
|
homeassistant/components/arduino.py
|
||||||
homeassistant/components/*/arduino.py
|
homeassistant/components/*/arduino.py
|
||||||
|
|
||||||
|
homeassistant/components/insteon_hub.py
|
||||||
|
homeassistant/components/*/insteon_hub.py
|
||||||
|
|
||||||
homeassistant/components/isy994.py
|
homeassistant/components/isy994.py
|
||||||
homeassistant/components/*/isy994.py
|
homeassistant/components/*/isy994.py
|
||||||
|
|
||||||
@ -32,6 +35,9 @@ omit =
|
|||||||
homeassistant/components/wink.py
|
homeassistant/components/wink.py
|
||||||
homeassistant/components/*/wink.py
|
homeassistant/components/*/wink.py
|
||||||
|
|
||||||
|
homeassistant/components/zigbee.py
|
||||||
|
homeassistant/components/*/zigbee.py
|
||||||
|
|
||||||
homeassistant/components/zwave.py
|
homeassistant/components/zwave.py
|
||||||
homeassistant/components/*/zwave.py
|
homeassistant/components/*/zwave.py
|
||||||
|
|
||||||
@ -41,6 +47,9 @@ omit =
|
|||||||
homeassistant/components/mysensors.py
|
homeassistant/components/mysensors.py
|
||||||
homeassistant/components/*/mysensors.py
|
homeassistant/components/*/mysensors.py
|
||||||
|
|
||||||
|
homeassistant/components/nest.py
|
||||||
|
homeassistant/components/*/nest.py
|
||||||
|
|
||||||
homeassistant/components/rpi_gpio.py
|
homeassistant/components/rpi_gpio.py
|
||||||
homeassistant/components/*/rpi_gpio.py
|
homeassistant/components/*/rpi_gpio.py
|
||||||
|
|
||||||
@ -57,7 +66,6 @@ omit =
|
|||||||
homeassistant/components/device_tracker/luci.py
|
homeassistant/components/device_tracker/luci.py
|
||||||
homeassistant/components/device_tracker/netgear.py
|
homeassistant/components/device_tracker/netgear.py
|
||||||
homeassistant/components/device_tracker/nmap_tracker.py
|
homeassistant/components/device_tracker/nmap_tracker.py
|
||||||
homeassistant/components/device_tracker/owntracks.py
|
|
||||||
homeassistant/components/device_tracker/snmp.py
|
homeassistant/components/device_tracker/snmp.py
|
||||||
homeassistant/components/device_tracker/thomson.py
|
homeassistant/components/device_tracker/thomson.py
|
||||||
homeassistant/components/device_tracker/tomato.py
|
homeassistant/components/device_tracker/tomato.py
|
||||||
@ -66,11 +74,13 @@ omit =
|
|||||||
homeassistant/components/discovery.py
|
homeassistant/components/discovery.py
|
||||||
homeassistant/components/downloader.py
|
homeassistant/components/downloader.py
|
||||||
homeassistant/components/ifttt.py
|
homeassistant/components/ifttt.py
|
||||||
|
homeassistant/components/statsd.py
|
||||||
homeassistant/components/influxdb.py
|
homeassistant/components/influxdb.py
|
||||||
homeassistant/components/keyboard.py
|
homeassistant/components/keyboard.py
|
||||||
homeassistant/components/light/blinksticklight.py
|
homeassistant/components/light/blinksticklight.py
|
||||||
homeassistant/components/light/hue.py
|
homeassistant/components/light/hue.py
|
||||||
homeassistant/components/light/hyperion.py
|
homeassistant/components/light/hyperion.py
|
||||||
|
homeassistant/components/light/lifx.py
|
||||||
homeassistant/components/light/limitlessled.py
|
homeassistant/components/light/limitlessled.py
|
||||||
homeassistant/components/media_player/cast.py
|
homeassistant/components/media_player/cast.py
|
||||||
homeassistant/components/media_player/denon.py
|
homeassistant/components/media_player/denon.py
|
||||||
@ -91,7 +101,9 @@ omit =
|
|||||||
homeassistant/components/notify/smtp.py
|
homeassistant/components/notify/smtp.py
|
||||||
homeassistant/components/notify/syslog.py
|
homeassistant/components/notify/syslog.py
|
||||||
homeassistant/components/notify/telegram.py
|
homeassistant/components/notify/telegram.py
|
||||||
|
homeassistant/components/notify/twitter.py
|
||||||
homeassistant/components/notify/xmpp.py
|
homeassistant/components/notify/xmpp.py
|
||||||
|
homeassistant/components/notify/googlevoice.py
|
||||||
homeassistant/components/sensor/arest.py
|
homeassistant/components/sensor/arest.py
|
||||||
homeassistant/components/sensor/bitcoin.py
|
homeassistant/components/sensor/bitcoin.py
|
||||||
homeassistant/components/sensor/cpuspeed.py
|
homeassistant/components/sensor/cpuspeed.py
|
||||||
@ -102,6 +114,7 @@ omit =
|
|||||||
homeassistant/components/sensor/forecast.py
|
homeassistant/components/sensor/forecast.py
|
||||||
homeassistant/components/sensor/glances.py
|
homeassistant/components/sensor/glances.py
|
||||||
homeassistant/components/sensor/netatmo.py
|
homeassistant/components/sensor/netatmo.py
|
||||||
|
homeassistant/components/sensor/onewire.py
|
||||||
homeassistant/components/sensor/openweathermap.py
|
homeassistant/components/sensor/openweathermap.py
|
||||||
homeassistant/components/sensor/rest.py
|
homeassistant/components/sensor/rest.py
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
@ -124,7 +137,6 @@ omit =
|
|||||||
homeassistant/components/thermostat/heatmiser.py
|
homeassistant/components/thermostat/heatmiser.py
|
||||||
homeassistant/components/thermostat/homematic.py
|
homeassistant/components/thermostat/homematic.py
|
||||||
homeassistant/components/thermostat/honeywell.py
|
homeassistant/components/thermostat/honeywell.py
|
||||||
homeassistant/components/thermostat/nest.py
|
|
||||||
homeassistant/components/thermostat/proliphix.py
|
homeassistant/components/thermostat/proliphix.py
|
||||||
homeassistant/components/thermostat/radiotherm.py
|
homeassistant/components/thermostat/radiotherm.py
|
||||||
|
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -69,6 +69,16 @@ nosetests.xml
|
|||||||
|
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
# emacs auto backups
|
||||||
|
*~
|
||||||
|
*#
|
||||||
|
*.orig
|
||||||
|
|
||||||
# venv stuff
|
# venv stuff
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
pip-selfcheck.json
|
pip-selfcheck.json
|
||||||
|
venv
|
||||||
|
|
||||||
|
# vimmy stuff
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
@ -29,9 +29,13 @@ import time
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
|
||||||
import homeassistant.loader as loader
|
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.helpers.event_decorators import \
|
||||||
|
track_state_change, track_time_change
|
||||||
|
from homeassistant.helpers.service import service
|
||||||
import homeassistant.components as core
|
import homeassistant.components as core
|
||||||
|
from homeassistant.components import device_tracker
|
||||||
|
from homeassistant.components import light
|
||||||
|
|
||||||
# The domain of your component. Should be equal to the name of your component
|
# The domain of your component. Should be equal to the name of your component
|
||||||
DOMAIN = "example"
|
DOMAIN = "example"
|
||||||
@ -39,11 +43,14 @@ DOMAIN = "example"
|
|||||||
# List of component names (string) your component depends upon
|
# List of component names (string) your component depends upon
|
||||||
# We depend on group because group will be loaded after all the components that
|
# We depend on group because group will be loaded after all the components that
|
||||||
# initialize devices have been setup.
|
# initialize devices have been setup.
|
||||||
DEPENDENCIES = ['group']
|
DEPENDENCIES = ['group', 'device_tracker', 'light']
|
||||||
|
|
||||||
# Configuration key for the entity id we are targetting
|
# Configuration key for the entity id we are targetting
|
||||||
CONF_TARGET = 'target'
|
CONF_TARGET = 'target'
|
||||||
|
|
||||||
|
# Variable for storing configuration parameters
|
||||||
|
TARGET_ID = None
|
||||||
|
|
||||||
# Name of the service that we expose
|
# Name of the service that we expose
|
||||||
SERVICE_FLASH = 'flash'
|
SERVICE_FLASH = 'flash'
|
||||||
|
|
||||||
@ -53,84 +60,89 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup example component. """
|
""" Setup example component. """
|
||||||
|
global TARGET_ID
|
||||||
|
|
||||||
# Validate that all required config options are given
|
# Validate that all required config options are given
|
||||||
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
|
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
target_id = config[DOMAIN][CONF_TARGET]
|
TARGET_ID = config[DOMAIN][CONF_TARGET]
|
||||||
|
|
||||||
# Validate that the target entity id exists
|
# Validate that the target entity id exists
|
||||||
if hass.states.get(target_id) is None:
|
if hass.states.get(TARGET_ID) is None:
|
||||||
_LOGGER.error("Target entity id %s does not exist", target_id)
|
_LOGGER.error("Target entity id %s does not exist",
|
||||||
|
TARGET_ID)
|
||||||
|
|
||||||
# Tell the bootstrapper that we failed to initialize
|
# Tell the bootstrapper that we failed to initialize and clear the
|
||||||
|
# stored target id so our functions don't run.
|
||||||
|
TARGET_ID = None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# We will use the component helper methods to check the states.
|
# Tell the bootstrapper that we initialized successfully
|
||||||
device_tracker = loader.get_component('device_tracker')
|
return True
|
||||||
light = loader.get_component('light')
|
|
||||||
|
|
||||||
def track_devices(entity_id, old_state, new_state):
|
|
||||||
|
@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES)
|
||||||
|
def track_devices(hass, entity_id, old_state, new_state):
|
||||||
""" Called when the group.all devices change state. """
|
""" Called when the group.all devices change state. """
|
||||||
|
# If the target id is not set, return
|
||||||
|
if not TARGET_ID:
|
||||||
|
return
|
||||||
|
|
||||||
# If anyone comes home and the core is not on, turn it on.
|
# If anyone comes home and the entity is not on, turn it on.
|
||||||
if new_state.state == STATE_HOME and not core.is_on(hass, target_id):
|
if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID):
|
||||||
|
|
||||||
core.turn_on(hass, target_id)
|
core.turn_on(hass, TARGET_ID)
|
||||||
|
|
||||||
# If all people leave the house and the core is on, turn it off
|
# If all people leave the house and the entity is on, turn it off
|
||||||
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, target_id):
|
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID):
|
||||||
|
|
||||||
core.turn_off(hass, target_id)
|
core.turn_off(hass, TARGET_ID)
|
||||||
|
|
||||||
# Register our track_devices method to receive state changes of the
|
|
||||||
# all tracked devices group.
|
|
||||||
hass.states.track_change(
|
|
||||||
device_tracker.ENTITY_ID_ALL_DEVICES, track_devices)
|
|
||||||
|
|
||||||
def wake_up(now):
|
@track_time_change(hour=7, minute=0, second=0)
|
||||||
""" Turn it on in the morning if there are people home and
|
def wake_up(hass, now):
|
||||||
it is not already on. """
|
"""
|
||||||
|
Turn it on in the morning (7 AM) if there are people home and
|
||||||
|
it is not already on.
|
||||||
|
"""
|
||||||
|
if not TARGET_ID:
|
||||||
|
return
|
||||||
|
|
||||||
if device_tracker.is_on(hass) and not core.is_on(hass, target_id):
|
if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID):
|
||||||
_LOGGER.info('People home at 7AM, turning it on')
|
_LOGGER.info('People home at 7AM, turning it on')
|
||||||
core.turn_on(hass, target_id)
|
core.turn_on(hass, TARGET_ID)
|
||||||
|
|
||||||
# Register our wake_up service to be called at 7AM in the morning
|
|
||||||
hass.track_time_change(wake_up, hour=7, minute=0, second=0)
|
|
||||||
|
|
||||||
def all_lights_off(entity_id, old_state, new_state):
|
@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF)
|
||||||
|
def all_lights_off(hass, entity_id, old_state, new_state):
|
||||||
""" If all lights turn off, turn off. """
|
""" If all lights turn off, turn off. """
|
||||||
|
if not TARGET_ID:
|
||||||
|
return
|
||||||
|
|
||||||
if core.is_on(hass, target_id):
|
if core.is_on(hass, TARGET_ID):
|
||||||
_LOGGER.info('All lights have been turned off, turning it off')
|
_LOGGER.info('All lights have been turned off, turning it off')
|
||||||
core.turn_off(hass, target_id)
|
core.turn_off(hass, TARGET_ID)
|
||||||
|
|
||||||
# Register our all_lights_off method to be called when all lights turn off
|
|
||||||
hass.states.track_change(
|
|
||||||
light.ENTITY_ID_ALL_LIGHTS, all_lights_off, STATE_ON, STATE_OFF)
|
|
||||||
|
|
||||||
def flash_service(call):
|
@service(DOMAIN, SERVICE_FLASH)
|
||||||
""" Service that will turn the target off for 10 seconds
|
def flash_service(hass, call):
|
||||||
if on and vice versa. """
|
"""
|
||||||
|
Service that will turn the target off for 10 seconds if on and vice versa.
|
||||||
|
"""
|
||||||
|
if not TARGET_ID:
|
||||||
|
return
|
||||||
|
|
||||||
if core.is_on(hass, target_id):
|
if core.is_on(hass, TARGET_ID):
|
||||||
core.turn_off(hass, target_id)
|
core.turn_off(hass, TARGET_ID)
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
core.turn_on(hass, target_id)
|
core.turn_on(hass, TARGET_ID)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
core.turn_on(hass, target_id)
|
core.turn_on(hass, TARGET_ID)
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
core.turn_off(hass, target_id)
|
core.turn_off(hass, TARGET_ID)
|
||||||
|
|
||||||
# Register our service with HASS.
|
|
||||||
hass.services.register(DOMAIN, SERVICE_FLASH, flash_service)
|
|
||||||
|
|
||||||
# Tells the bootstrapper that the component was successfully initialized
|
|
||||||
return True
|
|
||||||
|
@ -24,6 +24,7 @@ import homeassistant.config as config_util
|
|||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
import homeassistant.components.group as group
|
import homeassistant.components.group as group
|
||||||
|
from homeassistant.helpers import event_decorators, service
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
__version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
|
__version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
|
||||||
@ -199,6 +200,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
|||||||
|
|
||||||
_LOGGER.info('Home Assistant core initialized')
|
_LOGGER.info('Home Assistant core initialized')
|
||||||
|
|
||||||
|
# give event decorators access to HASS
|
||||||
|
event_decorators.HASS = hass
|
||||||
|
service.HASS = hass
|
||||||
|
|
||||||
# Setup the components
|
# Setup the components
|
||||||
for domain in loader.load_order_components(components):
|
for domain in loader.load_order_components(components):
|
||||||
_setup_component(hass, domain, config)
|
_setup_component(hass, domain, config)
|
||||||
@ -223,7 +228,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
|
|||||||
|
|
||||||
enable_logging(hass, verbose, daemon, log_rotate_days)
|
enable_logging(hass, verbose, daemon, log_rotate_days)
|
||||||
|
|
||||||
config_dict = config_util.load_config_file(config_path)
|
config_dict = config_util.load_yaml_config_file(config_path)
|
||||||
|
|
||||||
return from_config_dict(config_dict, hass, enable_log=False,
|
return from_config_dict(config_dict, hass, enable_log=False,
|
||||||
skip_pip=skip_pip)
|
skip_pip=skip_pip)
|
||||||
|
@ -16,11 +16,11 @@ import itertools as it
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.util as util
|
from homeassistant.helpers.entity import split_entity_id
|
||||||
from homeassistant.helpers import extract_entity_ids
|
from homeassistant.helpers.service import extract_entity_ids
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ def is_on(hass, entity_id=None):
|
|||||||
entity_ids = hass.states.entity_ids()
|
entity_ids = hass.states.entity_ids()
|
||||||
|
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
domain = util.split_entity_id(entity_id)[0]
|
domain = split_entity_id(entity_id)[0]
|
||||||
|
|
||||||
module = get_component(domain)
|
module = get_component(domain)
|
||||||
|
|
||||||
@ -68,6 +68,14 @@ def turn_off(hass, entity_id=None, **service_data):
|
|||||||
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
|
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(hass, entity_id=None, **service_data):
|
||||||
|
""" Toggles specified entity. """
|
||||||
|
if entity_id is not None:
|
||||||
|
service_data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup general services related to homeassistant. """
|
""" Setup general services related to homeassistant. """
|
||||||
|
|
||||||
@ -84,7 +92,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# Group entity_ids by domain. groupby requires sorted data.
|
# Group entity_ids by domain. groupby requires sorted data.
|
||||||
by_domain = it.groupby(sorted(entity_ids),
|
by_domain = it.groupby(sorted(entity_ids),
|
||||||
lambda item: util.split_entity_id(item)[0])
|
lambda item: split_entity_id(item)[0])
|
||||||
|
|
||||||
for domain, ent_ids in by_domain:
|
for domain, ent_ids in by_domain:
|
||||||
# We want to block for all calls and only return when all calls
|
# We want to block for all calls and only return when all calls
|
||||||
@ -105,5 +113,6 @@ def setup(hass, config):
|
|||||||
|
|
||||||
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
|
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
|
||||||
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
|
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
|
||||||
|
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -9,11 +9,6 @@ https://home-assistant.io/components/arduino/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
|
||||||
from PyMata.pymata import PyMata
|
|
||||||
except ImportError:
|
|
||||||
PyMata = None
|
|
||||||
|
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
@ -27,18 +22,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup the Arduino component. """
|
""" Setup the Arduino component. """
|
||||||
|
|
||||||
global PyMata # pylint: disable=invalid-name
|
|
||||||
if PyMata is None:
|
|
||||||
from PyMata.pymata import PyMata as PyMata_
|
|
||||||
PyMata = PyMata_
|
|
||||||
|
|
||||||
import serial
|
|
||||||
|
|
||||||
if not validate_config(config,
|
if not validate_config(config,
|
||||||
{DOMAIN: ['port']},
|
{DOMAIN: ['port']},
|
||||||
_LOGGER):
|
_LOGGER):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
import serial
|
||||||
global BOARD
|
global BOARD
|
||||||
try:
|
try:
|
||||||
BOARD = ArduinoBoard(config[DOMAIN]['port'])
|
BOARD = ArduinoBoard(config[DOMAIN]['port'])
|
||||||
@ -67,6 +56,7 @@ class ArduinoBoard(object):
|
|||||||
""" Represents an Arduino board. """
|
""" Represents an Arduino board. """
|
||||||
|
|
||||||
def __init__(self, port):
|
def __init__(self, port):
|
||||||
|
from PyMata.pymata import PyMata
|
||||||
self._port = port
|
self._port = port
|
||||||
self._board = PyMata(self._port, verbose=False)
|
self._board = PyMata(self._port, verbose=False)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import logging
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.components import sun
|
from homeassistant.components import sun
|
||||||
from homeassistant.helpers.event import track_point_in_utc_time
|
from homeassistant.helpers.event import track_sunrise, track_sunset
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
DEPENDENCIES = ['sun']
|
DEPENDENCIES = ['sun']
|
||||||
@ -47,9 +47,9 @@ def trigger(hass, config, action):
|
|||||||
|
|
||||||
# Do something to call action
|
# Do something to call action
|
||||||
if event == EVENT_SUNRISE:
|
if event == EVENT_SUNRISE:
|
||||||
trigger_sunrise(hass, action, offset)
|
track_sunrise(hass, action, offset)
|
||||||
else:
|
else:
|
||||||
trigger_sunset(hass, action, offset)
|
track_sunset(hass, action, offset)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -125,44 +125,6 @@ def if_action(hass, config):
|
|||||||
return time_if
|
return time_if
|
||||||
|
|
||||||
|
|
||||||
def trigger_sunrise(hass, action, offset):
|
|
||||||
""" Trigger action at next sun rise. """
|
|
||||||
def next_rise():
|
|
||||||
""" Returns next sunrise. """
|
|
||||||
next_time = sun.next_rising_utc(hass) + offset
|
|
||||||
|
|
||||||
while next_time < dt_util.utcnow():
|
|
||||||
next_time = next_time + timedelta(days=1)
|
|
||||||
|
|
||||||
return next_time
|
|
||||||
|
|
||||||
def sunrise_automation_listener(now):
|
|
||||||
""" Called when it's time for action. """
|
|
||||||
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
|
||||||
action()
|
|
||||||
|
|
||||||
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
|
||||||
|
|
||||||
|
|
||||||
def trigger_sunset(hass, action, offset):
|
|
||||||
""" Trigger action at next sun set. """
|
|
||||||
def next_set():
|
|
||||||
""" Returns next sunrise. """
|
|
||||||
next_time = sun.next_setting_utc(hass) + offset
|
|
||||||
|
|
||||||
while next_time < dt_util.utcnow():
|
|
||||||
next_time = next_time + timedelta(days=1)
|
|
||||||
|
|
||||||
return next_time
|
|
||||||
|
|
||||||
def sunset_automation_listener(now):
|
|
||||||
""" Called when it's time for action. """
|
|
||||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
|
||||||
action()
|
|
||||||
|
|
||||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_offset(raw_offset):
|
def _parse_offset(raw_offset):
|
||||||
if raw_offset is None:
|
if raw_offset is None:
|
||||||
return timedelta(0)
|
return timedelta(0)
|
||||||
|
@ -8,7 +8,6 @@ at https://home-assistant.io/components/automation/#time-trigger
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.util import convert
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.event import track_time_change
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
|
||||||
@ -34,9 +33,9 @@ def trigger(hass, config, action):
|
|||||||
hours, minutes, seconds = after.hour, after.minute, after.second
|
hours, minutes, seconds = after.hour, after.minute, after.second
|
||||||
elif (CONF_HOURS in config or CONF_MINUTES in config or
|
elif (CONF_HOURS in config or CONF_MINUTES in config or
|
||||||
CONF_SECONDS in config):
|
CONF_SECONDS in config):
|
||||||
hours = convert(config.get(CONF_HOURS), int)
|
hours = config.get(CONF_HOURS)
|
||||||
minutes = convert(config.get(CONF_MINUTES), int)
|
minutes = config.get(CONF_MINUTES)
|
||||||
seconds = convert(config.get(CONF_SECONDS), int)
|
seconds = config.get(CONF_SECONDS)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
|
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
|
||||||
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
|
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
|
||||||
@ -59,7 +58,7 @@ def if_action(hass, config):
|
|||||||
weekday = config.get(CONF_WEEKDAY)
|
weekday = config.get(CONF_WEEKDAY)
|
||||||
|
|
||||||
if before is None and after is None and weekday is None:
|
if before is None and after is None and weekday is None:
|
||||||
logging.getLogger(__name__).error(
|
_LOGGER.error(
|
||||||
"Missing if-condition configuration key %s, %s or %s",
|
"Missing if-condition configuration key %s, %s or %s",
|
||||||
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
||||||
return None
|
return None
|
||||||
|
81
homeassistant/components/binary_sensor/command_sensor.py
Normal file
81
homeassistant/components/binary_sensor/command_sensor.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.binary_sensor.command_sensor
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure custom shell commands to turn a value
|
||||||
|
into a logical value for a binary sensor.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.sensor.command_sensor import CommandSensorData
|
||||||
|
from homeassistant.util import template
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Binary Command Sensor"
|
||||||
|
DEFAULT_PAYLOAD_ON = 'ON'
|
||||||
|
DEFAULT_PAYLOAD_OFF = 'OFF'
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Add the Command Sensor. """
|
||||||
|
|
||||||
|
if config.get('command') is None:
|
||||||
|
_LOGGER.error('Missing required variable: "command"')
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = CommandSensorData(config.get('command'))
|
||||||
|
|
||||||
|
add_devices([CommandBinarySensor(
|
||||||
|
hass,
|
||||||
|
data,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('payload_on', DEFAULT_PAYLOAD_ON),
|
||||||
|
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
|
||||||
|
config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
)])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
class CommandBinarySensor(BinarySensorDevice):
|
||||||
|
""" Represents a binary sensor that is returning
|
||||||
|
a value of a shell commands. """
|
||||||
|
def __init__(self, hass, data, name, payload_on,
|
||||||
|
payload_off, value_template):
|
||||||
|
self._hass = hass
|
||||||
|
self.data = data
|
||||||
|
self._name = name
|
||||||
|
self._state = False
|
||||||
|
self._payload_on = payload_on
|
||||||
|
self._payload_off = payload_off
|
||||||
|
self._value_template = value_template
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if the binary sensor is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data and updates the state. """
|
||||||
|
self.data.update()
|
||||||
|
value = self.data.value
|
||||||
|
|
||||||
|
if self._value_template is not None:
|
||||||
|
value = template.render_with_possible_json_value(
|
||||||
|
self._hass, self._value_template, value, False)
|
||||||
|
if value == self._payload_on:
|
||||||
|
self._state = True
|
||||||
|
elif value == self._payload_off:
|
||||||
|
self._state = False
|
55
homeassistant/components/binary_sensor/nest.py
Normal file
55
homeassistant/components/binary_sensor/nest.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.binary_sensor.nest
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Nest Thermostat Binary Sensors.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/binary_sensor.nest/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import homeassistant.components.nest as nest
|
||||||
|
|
||||||
|
from homeassistant.components.sensor.nest import NestSensor
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
|
||||||
|
|
||||||
|
BINARY_TYPES = ['fan',
|
||||||
|
'hvac_ac_state',
|
||||||
|
'hvac_aux_heater_state',
|
||||||
|
'hvac_heat_x2_state',
|
||||||
|
'hvac_heat_x3_state',
|
||||||
|
'hvac_alt_heat_state',
|
||||||
|
'hvac_alt_heat_x2_state',
|
||||||
|
'hvac_emer_heat_state',
|
||||||
|
'online']
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Setup Nest binary sensors. """
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
try:
|
||||||
|
for structure in nest.NEST.structures:
|
||||||
|
for device in structure.devices:
|
||||||
|
for variable in config['monitored_conditions']:
|
||||||
|
if variable in BINARY_TYPES:
|
||||||
|
add_devices([NestBinarySensor(structure,
|
||||||
|
device,
|
||||||
|
variable)])
|
||||||
|
else:
|
||||||
|
logger.error('Nest sensor type: "%s" does not exist',
|
||||||
|
variable)
|
||||||
|
except socket.error:
|
||||||
|
logger.error(
|
||||||
|
"Connection error logging into the nest web service."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NestBinarySensor(NestSensor, BinarySensorDevice):
|
||||||
|
""" Represents a Nest binary sensor. """
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if the binary sensor is on. """
|
||||||
|
return bool(getattr(self.device, self.variable))
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.binary_sensor.rpi_gpio
|
homeassistant.components.binary_sensor.rpi_gpio
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Allows to configure a binary_sensor using RPi GPIO.
|
Allows to configure a binary 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/binary_sensor.rpi_gpio/
|
https://home-assistant.io/components/binary_sensor.rpi_gpio/
|
||||||
|
29
homeassistant/components/binary_sensor/zigbee.py
Normal file
29
homeassistant/components/binary_sensor/zigbee.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.binary_sensor.zigbee
|
||||||
|
|
||||||
|
Contains functionality to use a ZigBee device as a binary sensor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.zigbee import (
|
||||||
|
ZigBeeDigitalIn, ZigBeeDigitalInConfig)
|
||||||
|
|
||||||
|
|
||||||
|
DEPENDENCIES = ["zigbee"]
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""
|
||||||
|
Create and add an entity based on the configuration.
|
||||||
|
"""
|
||||||
|
add_entities([
|
||||||
|
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
|
||||||
|
"""
|
||||||
|
Use multiple inheritance to turn a ZigBeeDigitalIn into a
|
||||||
|
BinarySensorDevice.
|
||||||
|
"""
|
||||||
|
pass
|
@ -11,7 +11,7 @@ the user has submitted configuration information.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers import generate_entity_id
|
from homeassistant.helpers.entity import generate_entity_id
|
||||||
from homeassistant.const import EVENT_TIME_CHANGED
|
from homeassistant.const import EVENT_TIME_CHANGED
|
||||||
|
|
||||||
DOMAIN = "configurator"
|
DOMAIN = "configurator"
|
||||||
|
@ -62,10 +62,16 @@ def setup(hass, config):
|
|||||||
lights = sorted(hass.states.entity_ids('light'))
|
lights = sorted(hass.states.entity_ids('light'))
|
||||||
switches = sorted(hass.states.entity_ids('switch'))
|
switches = sorted(hass.states.entity_ids('switch'))
|
||||||
media_players = sorted(hass.states.entity_ids('media_player'))
|
media_players = sorted(hass.states.entity_ids('media_player'))
|
||||||
group.setup_group(hass, 'living room', [lights[2], lights[1], switches[0],
|
group.Group(hass, 'living room', [
|
||||||
media_players[1]])
|
lights[2], lights[1], switches[0], media_players[1],
|
||||||
group.setup_group(hass, 'bedroom', [lights[0], switches[1],
|
'scene.romantic_lights'])
|
||||||
|
group.Group(hass, 'bedroom', [lights[0], switches[1],
|
||||||
media_players[0]])
|
media_players[0]])
|
||||||
|
group.Group(hass, 'Rooms', [
|
||||||
|
'group.living_room', 'group.bedroom',
|
||||||
|
'scene.romantic_lights', 'rollershutter.kitchen_window',
|
||||||
|
'rollershutter.living_room_window',
|
||||||
|
], view=True)
|
||||||
|
|
||||||
# Setup scripts
|
# Setup scripts
|
||||||
bootstrap.setup_component(
|
bootstrap.setup_component(
|
||||||
|
@ -229,7 +229,7 @@ class DeviceTracker(object):
|
|||||||
""" Initializes group for all tracked devices. """
|
""" Initializes group for all tracked devices. """
|
||||||
entity_ids = (dev.entity_id for dev in self.devices.values()
|
entity_ids = (dev.entity_id for dev in self.devices.values()
|
||||||
if dev.track)
|
if dev.track)
|
||||||
self.group = group.setup_group(
|
self.group = group.Group(
|
||||||
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
||||||
|
|
||||||
def update_stale(self, now):
|
def update_stale(self, now):
|
||||||
|
@ -15,6 +15,8 @@ from homeassistant.helpers import validate_config
|
|||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
from homeassistant.components.device_tracker import DOMAIN
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
REQUIREMENTS = ['fritzconnection==0.4.6']
|
||||||
|
|
||||||
# Return cached results if last scan was less then this time ago
|
# Return cached results if last scan was less then this time ago
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
@ -55,16 +57,8 @@ class FritzBoxScanner(object):
|
|||||||
self.password = ''
|
self.password = ''
|
||||||
self.success_init = True
|
self.success_init = True
|
||||||
|
|
||||||
# Try to import the fritzconnection library
|
# pylint: disable=import-error
|
||||||
try:
|
|
||||||
# noinspection PyPackageRequirements,PyUnresolvedReferences
|
|
||||||
import fritzconnection as fc
|
import fritzconnection as fc
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception("""Failed to import Python library
|
|
||||||
fritzconnection. Please run
|
|
||||||
<home-assistant>/setup to install it.""")
|
|
||||||
self.success_init = False
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check for user specific configuration
|
# Check for user specific configuration
|
||||||
if CONF_HOST in config.keys():
|
if CONF_HOST in config.keys():
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.components.device_tracker import DOMAIN
|
|||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pynetgear==0.3.1']
|
REQUIREMENTS = ['pynetgear==0.3.2']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
|
@ -8,16 +8,26 @@ https://home-assistant.io/components/device_tracker.owntracks/
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import homeassistant.components.mqtt as mqtt
|
import homeassistant.components.mqtt as mqtt
|
||||||
from homeassistant.const import (STATE_HOME, STATE_NOT_HOME)
|
from homeassistant.const import STATE_HOME
|
||||||
|
|
||||||
DEPENDENCIES = ['mqtt']
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
CONF_TRANSITION_EVENTS = 'use_events'
|
REGIONS_ENTERED = defaultdict(list)
|
||||||
|
MOBILE_BEACONS_ACTIVE = defaultdict(list)
|
||||||
|
|
||||||
|
BEACON_DEV_ID = 'beacon'
|
||||||
|
|
||||||
LOCATION_TOPIC = 'owntracks/+/+'
|
LOCATION_TOPIC = 'owntracks/+/+'
|
||||||
EVENT_TOPIC = 'owntracks/+/+/event'
|
EVENT_TOPIC = 'owntracks/+/+/event'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def setup_scanner(hass, config, see):
|
def setup_scanner(hass, config, see):
|
||||||
""" Set up an OwnTracks tracker. """
|
""" Set up an OwnTracks tracker. """
|
||||||
@ -31,27 +41,28 @@ def setup_scanner(hass, config, see):
|
|||||||
data = json.loads(payload)
|
data = json.loads(payload)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If invalid JSON
|
# If invalid JSON
|
||||||
logging.getLogger(__name__).error(
|
_LOGGER.error(
|
||||||
'Unable to parse payload as JSON: %s', payload)
|
'Unable to parse payload as JSON: %s', payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(data, dict) or data.get('_type') != 'location':
|
if not isinstance(data, dict) or data.get('_type') != 'location':
|
||||||
return
|
return
|
||||||
|
|
||||||
parts = topic.split('/')
|
dev_id, kwargs = _parse_see_args(topic, data)
|
||||||
kwargs = {
|
|
||||||
'dev_id': '{}_{}'.format(parts[1], parts[2]),
|
# Block updates if we're in a region
|
||||||
'host_name': parts[1],
|
with LOCK:
|
||||||
'gps': (data['lat'], data['lon']),
|
if REGIONS_ENTERED[dev_id]:
|
||||||
}
|
_LOGGER.debug(
|
||||||
if 'acc' in data:
|
"location update ignored - inside region %s",
|
||||||
kwargs['gps_accuracy'] = data['acc']
|
REGIONS_ENTERED[-1])
|
||||||
if 'batt' in data:
|
return
|
||||||
kwargs['battery'] = data['batt']
|
|
||||||
|
|
||||||
see(**kwargs)
|
see(**kwargs)
|
||||||
|
see_beacons(dev_id, kwargs)
|
||||||
|
|
||||||
def owntracks_event_update(topic, payload, qos):
|
def owntracks_event_update(topic, payload, qos):
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
""" MQTT event (geofences) received. """
|
""" MQTT event (geofences) received. """
|
||||||
|
|
||||||
# Docs on available data:
|
# Docs on available data:
|
||||||
@ -60,47 +71,111 @@ def setup_scanner(hass, config, see):
|
|||||||
data = json.loads(payload)
|
data = json.loads(payload)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If invalid JSON
|
# If invalid JSON
|
||||||
logging.getLogger(__name__).error(
|
_LOGGER.error(
|
||||||
'Unable to parse payload as JSON: %s', payload)
|
'Unable to parse payload as JSON: %s', payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(data, dict) or data.get('_type') != 'transition':
|
if not isinstance(data, dict) or data.get('_type') != 'transition':
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if in "home" fence or other zone
|
# OwnTracks uses - at the start of a beacon zone
|
||||||
location = ''
|
# to switch on 'hold mode' - ignore this
|
||||||
if data['event'] == 'enter':
|
location = data['desc'].lstrip("-")
|
||||||
|
if location.lower() == 'home':
|
||||||
if data['desc'].lower() == 'home':
|
|
||||||
location = STATE_HOME
|
location = STATE_HOME
|
||||||
|
|
||||||
|
dev_id, kwargs = _parse_see_args(topic, data)
|
||||||
|
|
||||||
|
if data['event'] == 'enter':
|
||||||
|
zone = hass.states.get("zone.{}".format(location))
|
||||||
|
with LOCK:
|
||||||
|
if zone is None:
|
||||||
|
if data['t'] == 'b':
|
||||||
|
# Not a HA zone, and a beacon so assume mobile
|
||||||
|
MOBILE_BEACONS_ACTIVE[dev_id].append(location)
|
||||||
else:
|
else:
|
||||||
location = data['desc']
|
# Normal region
|
||||||
|
kwargs['location_name'] = location
|
||||||
|
|
||||||
|
regions = REGIONS_ENTERED[dev_id]
|
||||||
|
if location not in regions:
|
||||||
|
regions.append(location)
|
||||||
|
_LOGGER.info("Enter region %s", location)
|
||||||
|
_set_gps_from_zone(kwargs, zone)
|
||||||
|
|
||||||
|
see(**kwargs)
|
||||||
|
see_beacons(dev_id, kwargs)
|
||||||
|
|
||||||
elif data['event'] == 'leave':
|
elif data['event'] == 'leave':
|
||||||
location = STATE_NOT_HOME
|
regions = REGIONS_ENTERED[dev_id]
|
||||||
|
if location in regions:
|
||||||
|
regions.remove(location)
|
||||||
|
new_region = regions[-1] if regions else None
|
||||||
|
|
||||||
|
if new_region:
|
||||||
|
# Exit to previous region
|
||||||
|
zone = hass.states.get("zone.{}".format(new_region))
|
||||||
|
kwargs['location_name'] = new_region
|
||||||
|
_set_gps_from_zone(kwargs, zone)
|
||||||
|
_LOGGER.info("Exit from to %s", new_region)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.getLogger(__name__).error(
|
_LOGGER.info("Exit to GPS")
|
||||||
|
|
||||||
|
see(**kwargs)
|
||||||
|
see_beacons(dev_id, kwargs)
|
||||||
|
|
||||||
|
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
|
||||||
|
if location in beacons:
|
||||||
|
beacons.remove(location)
|
||||||
|
|
||||||
|
else:
|
||||||
|
_LOGGER.error(
|
||||||
'Misformatted mqtt msgs, _type=transition, event=%s',
|
'Misformatted mqtt msgs, _type=transition, event=%s',
|
||||||
data['event'])
|
data['event'])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def see_beacons(dev_id, kwargs_param):
|
||||||
|
""" Set active beacons to the current location """
|
||||||
|
|
||||||
|
kwargs = kwargs_param.copy()
|
||||||
|
for beacon in MOBILE_BEACONS_ACTIVE[dev_id]:
|
||||||
|
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
|
||||||
|
kwargs['host_name'] = beacon
|
||||||
|
see(**kwargs)
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_see_args(topic, data):
|
||||||
|
""" Parse the OwnTracks location parameters,
|
||||||
|
into the format see expects. """
|
||||||
|
|
||||||
parts = topic.split('/')
|
parts = topic.split('/')
|
||||||
|
dev_id = '{}_{}'.format(parts[1], parts[2])
|
||||||
|
host_name = parts[1]
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'dev_id': '{}_{}'.format(parts[1], parts[2]),
|
'dev_id': dev_id,
|
||||||
'host_name': parts[1],
|
'host_name': host_name,
|
||||||
'gps': (data['lat'], data['lon']),
|
'gps': (data['lat'], data['lon'])
|
||||||
'location_name': location,
|
|
||||||
}
|
}
|
||||||
if 'acc' in data:
|
if 'acc' in data:
|
||||||
kwargs['gps_accuracy'] = data['acc']
|
kwargs['gps_accuracy'] = data['acc']
|
||||||
|
if 'batt' in data:
|
||||||
|
kwargs['battery'] = data['batt']
|
||||||
|
return dev_id, kwargs
|
||||||
|
|
||||||
see(**kwargs)
|
|
||||||
|
|
||||||
use_events = config.get(CONF_TRANSITION_EVENTS)
|
def _set_gps_from_zone(kwargs, zone):
|
||||||
|
""" Set the see parameters from the zone parameters """
|
||||||
|
|
||||||
if use_events:
|
if zone is not None:
|
||||||
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
|
kwargs['gps'] = (
|
||||||
else:
|
zone.attributes['latitude'],
|
||||||
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
|
zone.attributes['longitude'])
|
||||||
|
kwargs['gps_accuracy'] = zone.attributes['radius']
|
||||||
return True
|
return kwargs
|
||||||
|
@ -11,6 +11,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.util import sanitize_filename
|
from homeassistant.util import sanitize_filename
|
||||||
|
|
||||||
@ -30,14 +32,6 @@ def setup(hass, config):
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
except ImportError:
|
|
||||||
logger.exception(("Failed to import requests. "
|
|
||||||
"Did you maybe not execute 'pip install requests'?"))
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
|
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
FRONTEND_URLS = [
|
FRONTEND_URLS = [
|
||||||
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
||||||
'/devEvent', '/devInfo', '/devTemplate', '/states']
|
'/devEvent', '/devInfo', '/devTemplate',
|
||||||
|
re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'),
|
||||||
|
]
|
||||||
|
|
||||||
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by update_mdi script """
|
""" DO NOT MODIFY. Auto-generated by update_mdi script """
|
||||||
VERSION = "7d76081c37634d36af21f5cc1ca79408"
|
VERSION = "a2605736c8d959d50c4bcbba1e6a6aa5"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "1003c31441ec44b3db84b49980f736a7"
|
VERSION = "1e89871aaae43c91b2508f52bc161b69"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 2ecd6a818443780dc5d0d981996d165218b2b094
|
Subproject commit 472f485a7e17d4ec4fc7cc6c17bcd6c41830d6fa
|
File diff suppressed because one or more lines are too long
@ -7,20 +7,24 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/group/
|
https://home-assistant.io/components/group/
|
||||||
"""
|
"""
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.helpers import generate_entity_id
|
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import (
|
||||||
import homeassistant.util as util
|
Entity, split_entity_id, generate_entity_id)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
|
ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
|
||||||
STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED,
|
STATE_HOME, STATE_NOT_HOME, STATE_OPEN, STATE_CLOSED,
|
||||||
STATE_UNKNOWN)
|
STATE_UNKNOWN, CONF_NAME, CONF_ICON)
|
||||||
|
|
||||||
DOMAIN = "group"
|
DOMAIN = 'group'
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
ATTR_AUTO = "auto"
|
CONF_ENTITIES = 'entities'
|
||||||
|
CONF_VIEW = 'view'
|
||||||
|
|
||||||
|
ATTR_AUTO = 'auto'
|
||||||
|
ATTR_ORDER = 'order'
|
||||||
|
ATTR_VIEW = 'view'
|
||||||
|
|
||||||
# List of ON/OFF state tuples for groupable states
|
# List of ON/OFF state tuples for groupable states
|
||||||
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
|
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
|
||||||
@ -62,7 +66,7 @@ def expand_entity_ids(hass, entity_ids):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# If entity_id points at a group, expand it
|
# If entity_id points at a group, expand it
|
||||||
domain, _ = util.split_entity_id(entity_id)
|
domain, _ = split_entity_id(entity_id)
|
||||||
|
|
||||||
if domain == DOMAIN:
|
if domain == DOMAIN:
|
||||||
found_ids.extend(
|
found_ids.extend(
|
||||||
@ -75,7 +79,7 @@ def expand_entity_ids(hass, entity_ids):
|
|||||||
found_ids.append(entity_id)
|
found_ids.append(entity_id)
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Raised by util.split_entity_id if entity_id is not a string
|
# Raised by split_entity_id if entity_id is not a string
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return found_ids
|
return found_ids
|
||||||
@ -104,10 +108,20 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up all groups found definded in the configuration. """
|
""" Sets up all groups found definded in the configuration. """
|
||||||
for name, entity_ids in config.get(DOMAIN, {}).items():
|
for object_id, conf in config.get(DOMAIN, {}).items():
|
||||||
|
if not isinstance(conf, dict):
|
||||||
|
conf = {CONF_ENTITIES: conf}
|
||||||
|
|
||||||
|
name = conf.get(CONF_NAME, object_id)
|
||||||
|
entity_ids = conf.get(CONF_ENTITIES)
|
||||||
|
icon = conf.get(CONF_ICON)
|
||||||
|
view = conf.get(CONF_VIEW)
|
||||||
|
|
||||||
if isinstance(entity_ids, str):
|
if isinstance(entity_ids, str):
|
||||||
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
|
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
|
||||||
setup_group(hass, name, entity_ids)
|
|
||||||
|
Group(hass, name, entity_ids, icon=icon, view=view,
|
||||||
|
object_id=object_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -115,14 +129,19 @@ def setup(hass, config):
|
|||||||
class Group(Entity):
|
class Group(Entity):
|
||||||
""" Tracks a group of entity ids. """
|
""" Tracks a group of entity ids. """
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||||
|
|
||||||
def __init__(self, hass, name, entity_ids=None, user_defined=True):
|
def __init__(self, hass, name, entity_ids=None, user_defined=True,
|
||||||
|
icon=None, view=False, object_id=None):
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = STATE_UNKNOWN
|
self._state = STATE_UNKNOWN
|
||||||
self.user_defined = user_defined
|
self._order = len(hass.states.entity_ids(DOMAIN))
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)
|
self._user_defined = user_defined
|
||||||
|
self._icon = icon
|
||||||
|
self._view = view
|
||||||
|
self.entity_id = generate_entity_id(
|
||||||
|
ENTITY_ID_FORMAT, object_id or name, hass=hass)
|
||||||
self.tracking = []
|
self.tracking = []
|
||||||
self.group_on = None
|
self.group_on = None
|
||||||
self.group_off = None
|
self.group_off = None
|
||||||
@ -144,12 +163,25 @@ class Group(Entity):
|
|||||||
def state(self):
|
def state(self):
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden(self):
|
||||||
|
return not self._user_defined or self._view
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
return {
|
data = {
|
||||||
ATTR_ENTITY_ID: self.tracking,
|
ATTR_ENTITY_ID: self.tracking,
|
||||||
ATTR_AUTO: not self.user_defined,
|
ATTR_ORDER: self._order,
|
||||||
}
|
}
|
||||||
|
if not self._user_defined:
|
||||||
|
data[ATTR_AUTO] = True
|
||||||
|
if self._view:
|
||||||
|
data[ATTR_VIEW] = True
|
||||||
|
return data
|
||||||
|
|
||||||
def update_tracked_entity_ids(self, entity_ids):
|
def update_tracked_entity_ids(self, entity_ids):
|
||||||
""" Update the tracked entity IDs. """
|
""" Update the tracked entity IDs. """
|
||||||
@ -220,10 +252,3 @@ class Group(Entity):
|
|||||||
for ent_id in self.tracking
|
for ent_id in self.tracking
|
||||||
if tr_state.entity_id != ent_id):
|
if tr_state.entity_id != ent_id):
|
||||||
self._state = group_off
|
self._state = group_off
|
||||||
|
|
||||||
|
|
||||||
def setup_group(hass, name, entity_ids, user_defined=True):
|
|
||||||
""" Sets up a group state that is the combined state of
|
|
||||||
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
|
||||||
|
|
||||||
return Group(hass, name, entity_ids, user_defined)
|
|
||||||
|
@ -23,7 +23,7 @@ DEFAULT_HOST = 'localhost'
|
|||||||
DEFAULT_PORT = 8086
|
DEFAULT_PORT = 8086
|
||||||
DEFAULT_DATABASE = 'home_assistant'
|
DEFAULT_DATABASE = 'home_assistant'
|
||||||
|
|
||||||
REQUIREMENTS = ['influxdb==2.10.0']
|
REQUIREMENTS = ['influxdb==2.11.0']
|
||||||
|
|
||||||
CONF_HOST = 'host'
|
CONF_HOST = 'host'
|
||||||
CONF_PORT = 'port'
|
CONF_PORT = 'port'
|
||||||
@ -51,14 +51,11 @@ def setup(hass, config):
|
|||||||
try:
|
try:
|
||||||
influx = InfluxDBClient(host=host, port=port, username=username,
|
influx = InfluxDBClient(host=host, port=port, username=username,
|
||||||
password=password, database=database)
|
password=password, database=database)
|
||||||
databases = [i['name'] for i in influx.get_list_database()]
|
influx.query("select * from /.*/ LIMIT 1;")
|
||||||
except exceptions.InfluxDBClientError:
|
except exceptions.InfluxDBClientError as exc:
|
||||||
_LOGGER.error("Database host is not accessible. "
|
_LOGGER.error("Database host is not accessible due to '%s', please "
|
||||||
"Please check your entries in the configuration file.")
|
"check your entries in the configuration file and that"
|
||||||
return False
|
" the database exists and is READ/WRITE.", exc)
|
||||||
|
|
||||||
if database not in databases:
|
|
||||||
_LOGGER.error("Database %s doesn't exist", database)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def influx_event_listener(event):
|
def influx_event_listener(event):
|
||||||
@ -76,6 +73,8 @@ def setup(hass, config):
|
|||||||
_state = 0
|
_state = 0
|
||||||
else:
|
else:
|
||||||
_state = state.state
|
_state = state.state
|
||||||
|
if _state == '':
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
_state = float(_state)
|
_state = float(_state)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -100,7 +99,7 @@ def setup(hass, config):
|
|||||||
try:
|
try:
|
||||||
influx.write_points(json_body)
|
influx.write_points(json_body)
|
||||||
except exceptions.InfluxDBClientError:
|
except exceptions.InfluxDBClientError:
|
||||||
_LOGGER.exception('Error saving event to InfluxDB')
|
_LOGGER.exception('Error saving event "%s" to InfluxDB', json_body)
|
||||||
|
|
||||||
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)
|
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
homeassistant.components.input_boolean
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Component to keep track of user controlled booleans for within automation.
|
Component to keep track of user controlled booleans for within automation.
|
||||||
|
|
||||||
For more details about this component, please refer to the documentation
|
For more details about this component, please refer to the documentation
|
||||||
|
93
homeassistant/components/insteon_hub.py
Normal file
93
homeassistant/components/insteon_hub.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.insteon
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Insteon Hub.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/insteon/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import homeassistant.bootstrap as bootstrap
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.loader import get_component
|
||||||
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY, ATTR_DISCOVERED,
|
||||||
|
ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED)
|
||||||
|
|
||||||
|
DOMAIN = "insteon_hub"
|
||||||
|
REQUIREMENTS = ['insteon_hub==0.4.5']
|
||||||
|
INSTEON = None
|
||||||
|
DISCOVER_LIGHTS = "insteon_hub.lights"
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""
|
||||||
|
Setup Insteon Hub component.
|
||||||
|
This will automatically import associated lights.
|
||||||
|
"""
|
||||||
|
if not validate_config(
|
||||||
|
config,
|
||||||
|
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
|
||||||
|
_LOGGER):
|
||||||
|
return False
|
||||||
|
|
||||||
|
import insteon
|
||||||
|
|
||||||
|
username = config[DOMAIN][CONF_USERNAME]
|
||||||
|
password = config[DOMAIN][CONF_PASSWORD]
|
||||||
|
api_key = config[DOMAIN][CONF_API_KEY]
|
||||||
|
|
||||||
|
global INSTEON
|
||||||
|
INSTEON = insteon.Insteon(username, password, api_key)
|
||||||
|
|
||||||
|
if INSTEON is None:
|
||||||
|
_LOGGER.error("Could not connect to Insteon service.")
|
||||||
|
return
|
||||||
|
|
||||||
|
comp_name = 'light'
|
||||||
|
discovery = DISCOVER_LIGHTS
|
||||||
|
component = get_component(comp_name)
|
||||||
|
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_PLATFORM_DISCOVERED,
|
||||||
|
{ATTR_SERVICE: discovery, ATTR_DISCOVERED: {}})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class InsteonToggleDevice(ToggleEntity):
|
||||||
|
""" Abstract Class for an Insteon node. """
|
||||||
|
|
||||||
|
def __init__(self, node):
|
||||||
|
self.node = node
|
||||||
|
self._value = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the node. """
|
||||||
|
return self.node.DeviceName
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
""" Returns the id of this insteon node. """
|
||||||
|
return self.node.DeviceID
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Update state of the sensor. """
|
||||||
|
resp = self.node.send_command('get_status', wait=True)
|
||||||
|
try:
|
||||||
|
self._value = resp['response']['level']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" Returns boolean response if the node is on. """
|
||||||
|
return self._value != 0
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
self.node.send_command('on')
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
self.node.send_command('off')
|
@ -37,11 +37,7 @@ def setup(hass, config):
|
|||||||
Setup ISY994 component.
|
Setup ISY994 component.
|
||||||
This will automatically import associated lights, switches, and sensors.
|
This will automatically import associated lights, switches, and sensors.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
import PyISY
|
import PyISY
|
||||||
except ImportError:
|
|
||||||
_LOGGER.error("Error while importing dependency PyISY.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# pylint: disable=global-statement
|
# pylint: disable=global-statement
|
||||||
# check for required values in configuration file
|
# check for required values in configuration file
|
||||||
|
@ -6,8 +6,6 @@ Provides functionality to emulate keyboard presses on host machine.
|
|||||||
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/keyboard/
|
https://home-assistant.io/components/keyboard/
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
||||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
|
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||||
@ -50,13 +48,7 @@ def media_prev_track(hass):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Listen for keyboard events. """
|
""" Listen for keyboard events. """
|
||||||
try:
|
|
||||||
import pykeyboard
|
import pykeyboard
|
||||||
except ImportError:
|
|
||||||
logging.getLogger(__name__).exception(
|
|
||||||
"Error while importing dependency PyUserInput.")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
keyboard = pykeyboard.PyKeyboard()
|
keyboard = pykeyboard.PyKeyboard()
|
||||||
keyboard.special_key_assignment()
|
keyboard.special_key_assignment()
|
||||||
|
@ -10,10 +10,12 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from homeassistant.components import group, discovery, wink, isy994, zwave
|
from homeassistant.components import (
|
||||||
|
group, discovery, wink, isy994, zwave, insteon_hub)
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||||
|
ATTR_ENTITY_ID)
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
@ -58,6 +60,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
|
|||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_LIGHTS: 'wink',
|
wink.DISCOVER_LIGHTS: 'wink',
|
||||||
|
insteon_hub.DISCOVER_LIGHTS: 'insteon_hub',
|
||||||
isy994.DISCOVER_LIGHTS: 'isy994',
|
isy994.DISCOVER_LIGHTS: 'isy994',
|
||||||
discovery.SERVICE_HUE: 'hue',
|
discovery.SERVICE_HUE: 'hue',
|
||||||
zwave.DISCOVER_LIGHTS: 'zwave',
|
zwave.DISCOVER_LIGHTS: 'zwave',
|
||||||
@ -114,6 +117,18 @@ def turn_off(hass, entity_id=None, transition=None):
|
|||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(hass, entity_id=None, transition=None):
|
||||||
|
""" Toggles all or specified light. """
|
||||||
|
data = {
|
||||||
|
key: value for key, value in [
|
||||||
|
(ATTR_ENTITY_ID, entity_id),
|
||||||
|
(ATTR_TRANSITION, transition),
|
||||||
|
] if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
|
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Exposes light control via statemachine and services. """
|
""" Exposes light control via statemachine and services. """
|
||||||
@ -165,9 +180,15 @@ def setup(hass, config):
|
|||||||
if transition is not None:
|
if transition is not None:
|
||||||
params[ATTR_TRANSITION] = transition
|
params[ATTR_TRANSITION] = transition
|
||||||
|
|
||||||
|
service_fun = None
|
||||||
if service.service == SERVICE_TURN_OFF:
|
if service.service == SERVICE_TURN_OFF:
|
||||||
|
service_fun = 'turn_off'
|
||||||
|
elif service.service == SERVICE_TOGGLE:
|
||||||
|
service_fun = 'toggle'
|
||||||
|
|
||||||
|
if service_fun:
|
||||||
for light in target_lights:
|
for light in target_lights:
|
||||||
light.turn_off(**params)
|
getattr(light, service_fun)(**params)
|
||||||
|
|
||||||
for light in target_lights:
|
for light in target_lights:
|
||||||
if light.should_poll:
|
if light.should_poll:
|
||||||
@ -249,6 +270,9 @@ def setup(hass, config):
|
|||||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service,
|
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service,
|
||||||
descriptions.get(SERVICE_TURN_OFF))
|
descriptions.get(SERVICE_TURN_OFF))
|
||||||
|
|
||||||
|
hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_light_service,
|
||||||
|
descriptions.get(SERVICE_TOGGLE))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
18
homeassistant/components/light/insteon_hub.py
Normal file
18
homeassistant/components/light/insteon_hub.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.light.insteon
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Insteon Hub lights.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from homeassistant.components.insteon_hub import (INSTEON, InsteonToggleDevice)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Insteon Hub light platform. """
|
||||||
|
devs = []
|
||||||
|
for device in INSTEON.devices:
|
||||||
|
if device.DeviceCategory == "Switched Lighting Control":
|
||||||
|
devs.append(InsteonToggleDevice(device))
|
||||||
|
if device.DeviceCategory == "Dimmable Lighting Control":
|
||||||
|
devs.append(InsteonToggleDevice(device))
|
||||||
|
add_devices(devs)
|
259
homeassistant/components/light/lifx.py
Normal file
259
homeassistant/components/light/lifx.py
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.light.lifx
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
LIFX platform that implements lights
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/light.lifx/
|
||||||
|
"""
|
||||||
|
# pylint: disable=missing-docstring
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import colorsys
|
||||||
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
from homeassistant.components.light import \
|
||||||
|
(Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['liffylights==0.9.0']
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
CONF_SERVER = "server" # server address configuration item
|
||||||
|
CONF_BROADCAST = "broadcast" # broadcast address configuration item
|
||||||
|
SHORT_MAX = 65535 # short int maximum
|
||||||
|
BYTE_MAX = 255 # byte maximum
|
||||||
|
TEMP_MIN = 2500 # lifx minimum temperature
|
||||||
|
TEMP_MAX = 9000 # lifx maximum temperature
|
||||||
|
TEMP_MIN_HASS = 154 # home assistant minimum temperature
|
||||||
|
TEMP_MAX_HASS = 500 # home assistant maximum temperature
|
||||||
|
|
||||||
|
|
||||||
|
class LIFX():
|
||||||
|
def __init__(self, add_devices_callback,
|
||||||
|
server_addr=None, broadcast_addr=None):
|
||||||
|
import liffylights
|
||||||
|
|
||||||
|
self._devices = []
|
||||||
|
|
||||||
|
self._add_devices_callback = add_devices_callback
|
||||||
|
|
||||||
|
self._liffylights = liffylights.LiffyLights(
|
||||||
|
self.on_device,
|
||||||
|
self.on_power,
|
||||||
|
self.on_color,
|
||||||
|
server_addr,
|
||||||
|
broadcast_addr)
|
||||||
|
|
||||||
|
def find_bulb(self, ipaddr):
|
||||||
|
bulb = None
|
||||||
|
for device in self._devices:
|
||||||
|
if device.ipaddr == ipaddr:
|
||||||
|
bulb = device
|
||||||
|
break
|
||||||
|
return bulb
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def on_device(self, ipaddr, name, power, hue, sat, bri, kel):
|
||||||
|
bulb = self.find_bulb(ipaddr)
|
||||||
|
|
||||||
|
if bulb is None:
|
||||||
|
bulb = LIFXLight(self._liffylights, ipaddr, name,
|
||||||
|
power, hue, sat, bri, kel)
|
||||||
|
self._devices.append(bulb)
|
||||||
|
self._add_devices_callback([bulb])
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def on_color(self, ipaddr, hue, sat, bri, kel):
|
||||||
|
bulb = self.find_bulb(ipaddr)
|
||||||
|
|
||||||
|
if bulb is not None:
|
||||||
|
bulb.set_color(hue, sat, bri, kel)
|
||||||
|
bulb.update_ha_state()
|
||||||
|
|
||||||
|
def on_power(self, ipaddr, power):
|
||||||
|
bulb = self.find_bulb(ipaddr)
|
||||||
|
|
||||||
|
if bulb is not None:
|
||||||
|
bulb.set_power(power)
|
||||||
|
bulb.update_ha_state()
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def poll(self, now):
|
||||||
|
self.probe()
|
||||||
|
|
||||||
|
def probe(self, address=None):
|
||||||
|
self._liffylights.probe(address)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Set up platform. """
|
||||||
|
server_addr = config.get(CONF_SERVER, None)
|
||||||
|
broadcast_addr = config.get(CONF_BROADCAST, None)
|
||||||
|
|
||||||
|
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
|
||||||
|
|
||||||
|
# register our poll service
|
||||||
|
track_time_change(hass, lifx_library.poll, second=10)
|
||||||
|
|
||||||
|
lifx_library.probe()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_rgb_to_hsv(rgb):
|
||||||
|
""" Convert HASS RGB values to HSV values. """
|
||||||
|
red, green, blue = [_ / BYTE_MAX for _ in rgb]
|
||||||
|
|
||||||
|
hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue)
|
||||||
|
|
||||||
|
return [int(hue * SHORT_MAX),
|
||||||
|
int(saturation * SHORT_MAX),
|
||||||
|
int(brightness * SHORT_MAX)]
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
class LIFXLight(Light):
|
||||||
|
""" Provides LIFX light. """
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self, liffy, ipaddr, name, power, hue,
|
||||||
|
saturation, brightness, kelvin):
|
||||||
|
_LOGGER.debug("LIFXLight: %s %s",
|
||||||
|
ipaddr, name)
|
||||||
|
|
||||||
|
self._liffylights = liffy
|
||||||
|
self._ip = ipaddr
|
||||||
|
self.set_name(name)
|
||||||
|
self.set_power(power)
|
||||||
|
self.set_color(hue, saturation, brightness, kelvin)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed for LIFX light. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ipaddr(self):
|
||||||
|
""" Returns the ip of the device. """
|
||||||
|
return self._ip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgb_color(self):
|
||||||
|
""" Returns RGB value. """
|
||||||
|
_LOGGER.debug("rgb_color: [%d %d %d]",
|
||||||
|
self._rgb[0], self._rgb[1], self._rgb[2])
|
||||||
|
|
||||||
|
return self._rgb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self):
|
||||||
|
""" Returns brightness of this light between 0..255. """
|
||||||
|
brightness = int(self._bri / (BYTE_MAX + 1))
|
||||||
|
|
||||||
|
_LOGGER.debug("brightness: %d",
|
||||||
|
brightness)
|
||||||
|
|
||||||
|
return brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp(self):
|
||||||
|
""" Returns color temperature. """
|
||||||
|
temperature = int(TEMP_MIN_HASS + (TEMP_MAX_HASS - TEMP_MIN_HASS) *
|
||||||
|
(self._kel - TEMP_MIN) / (TEMP_MAX - TEMP_MIN))
|
||||||
|
|
||||||
|
_LOGGER.debug("color_temp: %d",
|
||||||
|
temperature)
|
||||||
|
|
||||||
|
return temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
_LOGGER.debug("is_on: %d",
|
||||||
|
self._power)
|
||||||
|
|
||||||
|
return self._power != 0
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
if ATTR_TRANSITION in kwargs:
|
||||||
|
fade = kwargs[ATTR_TRANSITION] * 1000
|
||||||
|
else:
|
||||||
|
fade = 0
|
||||||
|
|
||||||
|
if ATTR_RGB_COLOR in kwargs:
|
||||||
|
hue, saturation, brightness = \
|
||||||
|
convert_rgb_to_hsv(kwargs[ATTR_RGB_COLOR])
|
||||||
|
else:
|
||||||
|
hue = self._hue
|
||||||
|
saturation = self._sat
|
||||||
|
brightness = self._bri
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS in kwargs:
|
||||||
|
brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1)
|
||||||
|
else:
|
||||||
|
brightness = self._bri
|
||||||
|
|
||||||
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
|
kelvin = int(((TEMP_MAX - TEMP_MIN) *
|
||||||
|
(kwargs[ATTR_COLOR_TEMP] - TEMP_MIN_HASS) /
|
||||||
|
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)
|
||||||
|
else:
|
||||||
|
kelvin = self._kel
|
||||||
|
|
||||||
|
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
|
||||||
|
self._ip, self._power,
|
||||||
|
hue, saturation, brightness, kelvin, fade)
|
||||||
|
|
||||||
|
if self._power == 0:
|
||||||
|
self._liffylights.set_power(self._ip, 65535, fade)
|
||||||
|
|
||||||
|
self._liffylights.set_color(self._ip, hue, saturation,
|
||||||
|
brightness, kelvin, fade)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
if ATTR_TRANSITION in kwargs:
|
||||||
|
fade = kwargs[ATTR_TRANSITION] * 1000
|
||||||
|
else:
|
||||||
|
fade = 0
|
||||||
|
|
||||||
|
_LOGGER.debug("turn_off: %s %d",
|
||||||
|
self._ip, fade)
|
||||||
|
|
||||||
|
self._liffylights.set_power(self._ip, 0, fade)
|
||||||
|
|
||||||
|
def set_name(self, name):
|
||||||
|
""" Set name. """
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def set_power(self, power):
|
||||||
|
""" Set power state value. """
|
||||||
|
_LOGGER.debug("set_power: %d",
|
||||||
|
power)
|
||||||
|
|
||||||
|
self._power = (power != 0)
|
||||||
|
|
||||||
|
def set_color(self, hue, sat, bri, kel):
|
||||||
|
""" Set color state values. """
|
||||||
|
self._hue = hue
|
||||||
|
self._sat = sat
|
||||||
|
self._bri = bri
|
||||||
|
self._kel = kel
|
||||||
|
|
||||||
|
red, green, blue = colorsys.hsv_to_rgb(hue / SHORT_MAX,
|
||||||
|
sat / SHORT_MAX,
|
||||||
|
bri / SHORT_MAX)
|
||||||
|
|
||||||
|
red = int(red * BYTE_MAX)
|
||||||
|
green = int(green * BYTE_MAX)
|
||||||
|
blue = int(blue * BYTE_MAX)
|
||||||
|
|
||||||
|
_LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
|
||||||
|
hue, sat, bri, kel, red, green, blue)
|
||||||
|
|
||||||
|
self._rgb = [red, green, blue]
|
@ -9,7 +9,7 @@ https://home-assistant.io/components/light.rfxtrx/
|
|||||||
import logging
|
import logging
|
||||||
import homeassistant.components.rfxtrx as rfxtrx
|
import homeassistant.components.rfxtrx as rfxtrx
|
||||||
|
|
||||||
from homeassistant.components.light import Light
|
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
@ -112,6 +112,7 @@ class RfxtrxLight(Light):
|
|||||||
self._event = event
|
self._event = event
|
||||||
self._state = datas[ATTR_STATE]
|
self._state = datas[ATTR_STATE]
|
||||||
self._should_fire_event = datas[ATTR_FIREEVENT]
|
self._should_fire_event = datas[ATTR_FIREEVENT]
|
||||||
|
self._brightness = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -133,12 +134,25 @@ class RfxtrxLight(Light):
|
|||||||
""" True if light is on. """
|
""" True if light is on. """
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self):
|
||||||
|
""" Brightness of this light between 0..255. """
|
||||||
|
return self._brightness
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turn the light on. """
|
""" Turn the light on. """
|
||||||
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
|
|
||||||
|
if brightness is None:
|
||||||
|
self._brightness = 100
|
||||||
|
else:
|
||||||
|
self._brightness = ((brightness + 4) * 100 // 255 - 1)
|
||||||
|
|
||||||
if hasattr(self, '_event') and self._event:
|
if hasattr(self, '_event') and self._event:
|
||||||
self._event.device.send_on(rfxtrx.RFXOBJECT.transport)
|
self._event.device.send_dim(rfxtrx.RFXOBJECT.transport,
|
||||||
|
self._brightness)
|
||||||
|
|
||||||
|
self._brightness = (self._brightness * 255 // 100)
|
||||||
self._state = True
|
self._state = True
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
@ -148,5 +162,6 @@ class RfxtrxLight(Light):
|
|||||||
if hasattr(self, '_event') and self._event:
|
if hasattr(self, '_event') and self._event:
|
||||||
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
|
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
|
||||||
|
|
||||||
|
self._brightness = 0
|
||||||
self._state = False
|
self._state = False
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
@ -55,3 +55,15 @@ turn_off:
|
|||||||
transition:
|
transition:
|
||||||
description: Duration in seconds it takes to get to next state
|
description: Duration in seconds it takes to get to next state
|
||||||
example: 60
|
example: 60
|
||||||
|
|
||||||
|
toggle:
|
||||||
|
description: Toggles a light
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities to toggle
|
||||||
|
example: 'light.kitchen'
|
||||||
|
|
||||||
|
transition:
|
||||||
|
description: Duration in seconds it takes to get to next state
|
||||||
|
example: 60
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.4.1']
|
REQUIREMENTS = ['python-wink==0.4.2']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
29
homeassistant/components/light/zigbee.py
Normal file
29
homeassistant/components/light/zigbee.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.light.zigbee
|
||||||
|
|
||||||
|
Contains functionality to use a ZigBee device as a light.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from homeassistant.components.light import Light
|
||||||
|
from homeassistant.components.zigbee import (
|
||||||
|
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
|
||||||
|
|
||||||
|
|
||||||
|
DEPENDENCIES = ["zigbee"]
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""
|
||||||
|
Create and add an entity based on the configuration.
|
||||||
|
"""
|
||||||
|
add_entities([
|
||||||
|
ZigBeeLight(hass, ZigBeeDigitalOutConfig(config))
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeLight(ZigBeeDigitalOut, Light):
|
||||||
|
"""
|
||||||
|
Use multiple inheritance to turn an instance of ZigBeeDigitalOut into a
|
||||||
|
Light.
|
||||||
|
"""
|
||||||
|
pass
|
@ -11,8 +11,10 @@ https://home-assistant.io/components/light.zwave/
|
|||||||
from threading import Timer
|
from threading import Timer
|
||||||
|
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS)
|
from homeassistant.components.light import Light, ATTR_BRIGHTNESS, DOMAIN
|
||||||
import homeassistant.components.zwave as zwave
|
from homeassistant.components.zwave import (
|
||||||
|
COMMAND_CLASS_SWITCH_MULTILEVEL, TYPE_BYTE, GENRE_USER, NETWORK,
|
||||||
|
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -20,14 +22,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
|
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||||
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
|
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||||
|
|
||||||
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
|
if value.command_class != COMMAND_CLASS_SWITCH_MULTILEVEL:
|
||||||
return
|
return
|
||||||
if value.type != zwave.TYPE_BYTE:
|
if value.type != TYPE_BYTE:
|
||||||
return
|
return
|
||||||
if value.genre != zwave.GENRE_USER:
|
if value.genre != GENRE_USER:
|
||||||
return
|
return
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
@ -45,15 +47,14 @@ def brightness_state(value):
|
|||||||
return 255, STATE_OFF
|
return 255, STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
class ZwaveDimmer(Light):
|
class ZwaveDimmer(ZWaveDeviceEntity, 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 openzwave.network import ZWaveNetwork
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
self._value = value
|
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||||
self._node = value.node
|
|
||||||
|
|
||||||
self._brightness, self._state = brightness_state(value)
|
self._brightness, self._state = brightness_state(value)
|
||||||
|
|
||||||
@ -86,18 +87,6 @@ class ZwaveDimmer(Light):
|
|||||||
|
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
""" No polling needed for a light. """
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
""" Returns the name of the device if any. """
|
|
||||||
name = self._node.name or "{}".format(self._node.product_name)
|
|
||||||
|
|
||||||
return "{}".format(name or self._value.label)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
""" Brightness of this light between 0..255. """
|
""" Brightness of this light between 0..255. """
|
||||||
@ -118,10 +107,10 @@ class ZwaveDimmer(Light):
|
|||||||
# brightness.
|
# brightness.
|
||||||
brightness = (self._brightness / 255) * 99
|
brightness = (self._brightness / 255) * 99
|
||||||
|
|
||||||
if self._node.set_dimmer(self._value.value_id, brightness):
|
if self._value.node.set_dimmer(self._value.value_id, brightness):
|
||||||
self._state = STATE_ON
|
self._state = STATE_ON
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" Turn the device off. """
|
""" Turn the device off. """
|
||||||
if self._node.set_dimmer(self._value.value_id, 0):
|
if self._value.node.set_dimmer(self._value.value_id, 0):
|
||||||
self._state = STATE_OFF
|
self._state = STATE_OFF
|
||||||
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.4.1']
|
REQUIREMENTS = ['python-wink==0.4.2']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -6,6 +6,7 @@ Parses events and generates a human log.
|
|||||||
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/logbook/
|
https://home-assistant.io/components/logbook/
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
import re
|
import re
|
||||||
@ -14,10 +15,10 @@ from homeassistant.core import State, DOMAIN as HA_DOMAIN
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_STATE_CHANGED, STATE_NOT_HOME, STATE_ON, STATE_OFF,
|
EVENT_STATE_CHANGED, STATE_NOT_HOME, STATE_ON, STATE_OFF,
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST)
|
||||||
from homeassistant import util
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components import recorder, sun
|
from homeassistant.components import recorder, sun
|
||||||
|
from homeassistant.helpers.entity import split_entity_id
|
||||||
|
from homeassistant.util import template
|
||||||
|
|
||||||
DOMAIN = "logbook"
|
DOMAIN = "logbook"
|
||||||
DEPENDENCIES = ['recorder', 'http']
|
DEPENDENCIES = ['recorder', 'http']
|
||||||
@ -28,6 +29,8 @@ QUERY_EVENTS_BETWEEN = """
|
|||||||
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
|
SELECT * FROM events WHERE time_fired > ? AND time_fired < ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
|
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
|
||||||
|
|
||||||
GROUP_BY_MINUTES = 15
|
GROUP_BY_MINUTES = 15
|
||||||
@ -54,8 +57,22 @@ def log_entry(hass, name, message, domain=None, entity_id=None):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Listens for download events to download files. """
|
""" Listens for download events to download files. """
|
||||||
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
|
# create service handler
|
||||||
|
def log_message(service):
|
||||||
|
""" Handle sending notification message service calls. """
|
||||||
|
message = service.data.get(ATTR_MESSAGE)
|
||||||
|
name = service.data.get(ATTR_NAME)
|
||||||
|
domain = service.data.get(ATTR_DOMAIN, None)
|
||||||
|
entity_id = service.data.get(ATTR_ENTITY_ID, None)
|
||||||
|
|
||||||
|
if not message or not name:
|
||||||
|
return
|
||||||
|
|
||||||
|
message = template.render(hass, message)
|
||||||
|
log_entry(hass, name, message, domain, entity_id)
|
||||||
|
|
||||||
|
hass.http.register_path('GET', URL_LOGBOOK, _handle_get_logbook)
|
||||||
|
hass.services.register(DOMAIN, 'log', log_message)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -209,7 +226,7 @@ def humanify(events):
|
|||||||
entity_id = event.data.get(ATTR_ENTITY_ID)
|
entity_id = event.data.get(ATTR_ENTITY_ID)
|
||||||
if domain is None and entity_id is not None:
|
if domain is None and entity_id is not None:
|
||||||
try:
|
try:
|
||||||
domain = util.split_entity_id(str(entity_id))[0]
|
domain = split_entity_id(str(entity_id))[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ from homeassistant.config import load_yaml_config_file
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING,
|
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE,
|
||||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||||
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
|
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
|
||||||
SERVICE_VOLUME_MUTE,
|
SERVICE_VOLUME_MUTE, SERVICE_TOGGLE,
|
||||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
||||||
|
|
||||||
@ -79,6 +79,7 @@ YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
|
|||||||
SERVICE_TO_METHOD = {
|
SERVICE_TO_METHOD = {
|
||||||
SERVICE_TURN_ON: 'turn_on',
|
SERVICE_TURN_ON: 'turn_on',
|
||||||
SERVICE_TURN_OFF: 'turn_off',
|
SERVICE_TURN_OFF: 'turn_off',
|
||||||
|
SERVICE_TOGGLE: 'toggle',
|
||||||
SERVICE_VOLUME_UP: 'volume_up',
|
SERVICE_VOLUME_UP: 'volume_up',
|
||||||
SERVICE_VOLUME_DOWN: 'volume_down',
|
SERVICE_VOLUME_DOWN: 'volume_down',
|
||||||
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
|
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
|
||||||
@ -131,6 +132,12 @@ def turn_off(hass, entity_id=None):
|
|||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(hass, entity_id=None):
|
||||||
|
""" Will toggle specified media player or all. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
|
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||||
|
|
||||||
|
|
||||||
def volume_up(hass, entity_id=None):
|
def volume_up(hass, entity_id=None):
|
||||||
""" Send the media player the command for volume up. """
|
""" Send the media player the command for volume up. """
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
@ -532,6 +539,13 @@ class MediaPlayerDevice(Entity):
|
|||||||
""" Boolean if play media command supported. """
|
""" Boolean if play media command supported. """
|
||||||
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
|
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
|
||||||
|
|
||||||
|
def toggle(self):
|
||||||
|
""" Toggles the power on the media player. """
|
||||||
|
if self.state in [STATE_OFF, STATE_IDLE]:
|
||||||
|
self.turn_on()
|
||||||
|
else:
|
||||||
|
self.turn_off()
|
||||||
|
|
||||||
def volume_up(self):
|
def volume_up(self):
|
||||||
""" volume_up media player. """
|
""" volume_up media player. """
|
||||||
if self.volume_level < 1:
|
if self.volume_level < 1:
|
||||||
|
@ -20,7 +20,7 @@ from homeassistant.components.media_player import (
|
|||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||||
|
|
||||||
REQUIREMENTS = ['pychromecast==0.6.14']
|
REQUIREMENTS = ['pychromecast==0.7.1']
|
||||||
CONF_IGNORE_CEC = 'ignore_cec'
|
CONF_IGNORE_CEC = 'ignore_cec'
|
||||||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
|
@ -14,8 +14,7 @@ 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,
|
||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
|
||||||
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
|
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA)
|
||||||
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS)
|
|
||||||
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)
|
||||||
|
|
||||||
@ -40,7 +39,10 @@ class Itunes(object):
|
|||||||
@property
|
@property
|
||||||
def _base_url(self):
|
def _base_url(self):
|
||||||
""" Returns the base url for endpoints. """
|
""" Returns the base url for endpoints. """
|
||||||
|
if self.port:
|
||||||
return self.host + ":" + str(self.port)
|
return self.host + ":" + str(self.port)
|
||||||
|
else:
|
||||||
|
return self.host
|
||||||
|
|
||||||
def _request(self, method, path, params=None):
|
def _request(self, method, path, params=None):
|
||||||
""" Makes the actual request and returns the parsed response. """
|
""" Makes the actual request and returns the parsed response. """
|
||||||
@ -380,6 +382,14 @@ class AirPlayDevice(MediaPlayerDevice):
|
|||||||
""" Returns the name of the device. """
|
""" Returns the name of the device. """
|
||||||
return self.device_name
|
return self.device_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
""" Icon to use in the frontend, if any. """
|
||||||
|
if self.selected is True:
|
||||||
|
return "mdi:volume-high"
|
||||||
|
else:
|
||||||
|
return "mdi:volume-off"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
@ -405,23 +415,6 @@ class AirPlayDevice(MediaPlayerDevice):
|
|||||||
""" Flags of media commands that are supported. """
|
""" Flags of media commands that are supported. """
|
||||||
return SUPPORT_AIRPLAY
|
return SUPPORT_AIRPLAY
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
""" Return the state attributes. """
|
|
||||||
state_attr = {}
|
|
||||||
state_attr[ATTR_SUPPORTED_MEDIA_COMMANDS] = SUPPORT_AIRPLAY
|
|
||||||
|
|
||||||
if self.state == STATE_OFF:
|
|
||||||
state_attr[ATTR_ENTITY_PICTURE] = \
|
|
||||||
('https://cloud.githubusercontent.com/assets/260/9833073'
|
|
||||||
'/6eb5c906-5958-11e5-9b4a-472cdf36be16.png')
|
|
||||||
else:
|
|
||||||
state_attr[ATTR_ENTITY_PICTURE] = \
|
|
||||||
('https://cloud.githubusercontent.com/assets/260/9833072'
|
|
||||||
'/6eb13cce-5958-11e5-996f-e2aaefbc9a24.png')
|
|
||||||
|
|
||||||
return state_attr
|
|
||||||
|
|
||||||
def set_volume_level(self, volume):
|
def set_volume_level(self, volume):
|
||||||
""" set volume level, range 0..1. """
|
""" set volume level, range 0..1. """
|
||||||
volume = int(volume * 100)
|
volume = int(volume * 100)
|
||||||
|
@ -72,7 +72,8 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
try:
|
try:
|
||||||
return self._server.Player.GetActivePlayers()
|
return self._server.Player.GetActivePlayers()
|
||||||
except jsonrpc_requests.jsonrpc.TransportError:
|
except jsonrpc_requests.jsonrpc.TransportError:
|
||||||
_LOGGER.exception('Unable to fetch kodi data')
|
_LOGGER.warning('Unable to fetch kodi data')
|
||||||
|
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -9,11 +9,6 @@ https://home-assistant.io/components/media_player.mpd/
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
try:
|
|
||||||
import mpd
|
|
||||||
except ImportError:
|
|
||||||
mpd = None
|
|
||||||
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||||
@ -40,10 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
location = config.get('location', 'MPD')
|
location = config.get('location', 'MPD')
|
||||||
password = config.get('password', None)
|
password = config.get('password', None)
|
||||||
|
|
||||||
global mpd # pylint: disable=invalid-name
|
import mpd
|
||||||
if mpd is None:
|
|
||||||
import mpd as mpd_
|
|
||||||
mpd = mpd_
|
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
try:
|
try:
|
||||||
@ -82,6 +74,8 @@ class MpdDevice(MediaPlayerDevice):
|
|||||||
# pylint: disable=no-member, abstract-method
|
# pylint: disable=no-member, abstract-method
|
||||||
|
|
||||||
def __init__(self, server, port, location, password):
|
def __init__(self, server, port, location, password):
|
||||||
|
import mpd
|
||||||
|
|
||||||
self.server = server
|
self.server = server
|
||||||
self.port = port
|
self.port = port
|
||||||
self._name = location
|
self._name = location
|
||||||
@ -95,6 +89,7 @@ class MpdDevice(MediaPlayerDevice):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
import mpd
|
||||||
try:
|
try:
|
||||||
self.status = self.client.status()
|
self.status = self.client.status()
|
||||||
self.currentsong = self.client.currentsong()
|
self.currentsong = self.client.currentsong()
|
||||||
|
@ -112,7 +112,7 @@ def setup_plexserver(host, token, hass, add_devices_callback):
|
|||||||
{host: {'token': token}}):
|
{host: {'token': token}}):
|
||||||
_LOGGER.error('failed to save config file')
|
_LOGGER.error('failed to save config file')
|
||||||
|
|
||||||
_LOGGER.info('Connected to: htts://%s', host)
|
_LOGGER.info('Connected to: http://%s', host)
|
||||||
|
|
||||||
plex_clients = {}
|
plex_clients = {}
|
||||||
plex_sessions = {}
|
plex_sessions = {}
|
||||||
|
@ -38,12 +38,23 @@ SUPPORT_SONOS = 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 Sonos platform. """
|
""" Sets up the Sonos platform. """
|
||||||
import soco
|
import soco
|
||||||
|
import socket
|
||||||
|
|
||||||
if discovery_info:
|
if discovery_info:
|
||||||
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))])
|
add_devices([SonosDevice(hass, soco.SoCo(discovery_info))])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
players = soco.discover()
|
players = None
|
||||||
|
hosts = config.get('hosts', None)
|
||||||
|
if hosts:
|
||||||
|
players = []
|
||||||
|
for host in hosts.split(","):
|
||||||
|
host = socket.gethostbyname(host)
|
||||||
|
players.append(soco.SoCo(host))
|
||||||
|
|
||||||
|
if not players:
|
||||||
|
players = soco.discover(interface_addr=config.get('interface_addr',
|
||||||
|
None))
|
||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
_LOGGER.warning('No Sonos speakers found.')
|
_LOGGER.warning('No Sonos speakers found.')
|
||||||
|
@ -201,12 +201,20 @@ class SqueezeBoxDevice(MediaPlayerDevice):
|
|||||||
def media_image_url(self):
|
def media_image_url(self):
|
||||||
""" Image url of current playing media. """
|
""" Image url of current playing media. """
|
||||||
if 'artwork_url' in self._status:
|
if 'artwork_url' in self._status:
|
||||||
return self._status['artwork_url']
|
media_url = self._status['artwork_url']
|
||||||
return ('http://{server}:{port}/music/current/cover.jpg?'
|
elif 'id' in self._status:
|
||||||
'player={player}').format(server=self._lms.host,
|
media_url = ('/music/{track_id}/cover.jpg').format(
|
||||||
port=self._lms.http_port,
|
track_id=self._status['id'])
|
||||||
|
else:
|
||||||
|
media_url = ('/music/current/cover.jpg?player={player}').format(
|
||||||
player=self._id)
|
player=self._id)
|
||||||
|
|
||||||
|
base_url = 'http://{server}:{port}/'.format(
|
||||||
|
server=self._lms.host,
|
||||||
|
port=self._lms.http_port)
|
||||||
|
|
||||||
|
return urllib.parse.urljoin(base_url, media_url)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_title(self):
|
def media_title(self):
|
||||||
""" Title of current playing media. """
|
""" Title of current playing media. """
|
||||||
|
@ -1,20 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.mqtt_eventstream
|
homeassistant.components.mqtt_eventstream
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Connect two Home Assistant instances via mqtt.
|
Connect two Home Assistant instances via MQTT..
|
||||||
|
|
||||||
Configuration:
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/mqtt_eventstream.html
|
||||||
To use the mqtt_eventstream component you will need to add the following to
|
|
||||||
your configuration.yaml file.
|
|
||||||
|
|
||||||
If you do not specify a publish_topic you will not forward events to the queue.
|
|
||||||
If you do not specify a subscribe_topic then you will not receive events from
|
|
||||||
the remote server.
|
|
||||||
|
|
||||||
mqtt_eventstream:
|
|
||||||
publish_topic: MyServerName
|
|
||||||
subscribe_topic: OtherHaServerName
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
from homeassistant.core import EventOrigin, State
|
from homeassistant.core import EventOrigin, State
|
||||||
@ -38,13 +28,13 @@ DEPENDENCIES = ['mqtt']
|
|||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup our mqtt_eventstream component. """
|
""" Setup th MQTT eventstream component. """
|
||||||
mqtt = loader.get_component('mqtt')
|
mqtt = loader.get_component('mqtt')
|
||||||
pub_topic = config[DOMAIN].get('publish_topic', None)
|
pub_topic = config[DOMAIN].get('publish_topic', None)
|
||||||
sub_topic = config[DOMAIN].get('subscribe_topic', None)
|
sub_topic = config[DOMAIN].get('subscribe_topic', None)
|
||||||
|
|
||||||
def _event_publisher(event):
|
def _event_publisher(event):
|
||||||
""" Handle events by publishing them on the mqtt queue. """
|
""" Handle events by publishing them on the MQTT queue. """
|
||||||
if event.origin != EventOrigin.local:
|
if event.origin != EventOrigin.local:
|
||||||
return
|
return
|
||||||
if event.event_type == EVENT_TIME_CHANGED:
|
if event.event_type == EVENT_TIME_CHANGED:
|
||||||
|
@ -1,32 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.mysensors
|
homeassistant.components.mysensors
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
MySensors component that connects to a MySensors gateway via pymysensors
|
MySensors component that connects to a MySensors gateway via pymysensors
|
||||||
API.
|
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.mysensors.html
|
https://home-assistant.io/components/sensor.mysensors/
|
||||||
|
|
||||||
|
|
||||||
New features:
|
|
||||||
|
|
||||||
New MySensors component.
|
|
||||||
Updated MySensors Sensor platform.
|
|
||||||
New MySensors Switch platform. Currently only in optimistic mode (compare
|
|
||||||
with MQTT).
|
|
||||||
Multiple gateways are now supported.
|
|
||||||
|
|
||||||
Configuration.yaml:
|
|
||||||
|
|
||||||
mysensors:
|
|
||||||
gateways:
|
|
||||||
- port: '/dev/ttyUSB0'
|
|
||||||
persistence_file: 'path/mysensors.json'
|
|
||||||
- port: '/dev/ttyACM1'
|
|
||||||
persistence_file: 'path/mysensors2.json'
|
|
||||||
debug: true
|
|
||||||
persistence: true
|
|
||||||
version: '1.5'
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -58,7 +37,6 @@ ATTR_CHILD_ID = 'child_id'
|
|||||||
ATTR_PORT = 'port'
|
ATTR_PORT = 'port'
|
||||||
|
|
||||||
GATEWAYS = None
|
GATEWAYS = None
|
||||||
SCAN_INTERVAL = 30
|
|
||||||
|
|
||||||
DISCOVER_SENSORS = "mysensors.sensors"
|
DISCOVER_SENSORS = "mysensors.sensors"
|
||||||
DISCOVER_SWITCHES = "mysensors.switches"
|
DISCOVER_SWITCHES = "mysensors.switches"
|
||||||
@ -135,36 +113,32 @@ def setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def pf_callback_factory(
|
def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
|
||||||
s_types, v_types, devices, add_devices, entity_class):
|
|
||||||
"""Return a new callback for the platform."""
|
"""Return a new callback for the platform."""
|
||||||
def mysensors_callback(gateway, node_id):
|
def mysensors_callback(gateway, node_id):
|
||||||
"""Callback for mysensors platform."""
|
"""Callback for mysensors platform."""
|
||||||
if gateway.sensors[node_id].sketch_name is None:
|
if gateway.sensors[node_id].sketch_name is None:
|
||||||
_LOGGER.info('No sketch_name: node %s', node_id)
|
_LOGGER.info('No sketch_name: node %s', node_id)
|
||||||
return
|
return
|
||||||
# previously discovered, just update state with latest info
|
|
||||||
if node_id in devices:
|
|
||||||
for entity in devices[node_id]:
|
|
||||||
entity.update_ha_state(True)
|
|
||||||
return
|
|
||||||
|
|
||||||
# First time we see this node, detect sensors
|
|
||||||
for child in gateway.sensors[node_id].children.values():
|
for child in gateway.sensors[node_id].children.values():
|
||||||
|
for value_type in child.values.keys():
|
||||||
|
key = node_id, child.id, value_type
|
||||||
|
if child.type not in map_sv_types or \
|
||||||
|
value_type not in map_sv_types[child.type]:
|
||||||
|
continue
|
||||||
|
if key in devices:
|
||||||
|
devices[key].update_ha_state(True)
|
||||||
|
continue
|
||||||
name = '{} {}.{}'.format(
|
name = '{} {}.{}'.format(
|
||||||
gateway.sensors[node_id].sketch_name, node_id, child.id)
|
gateway.sensors[node_id].sketch_name, node_id, child.id)
|
||||||
|
devices[key] = entity_class(
|
||||||
|
gateway, node_id, child.id, name, value_type)
|
||||||
|
|
||||||
for value_type in child.values.keys():
|
_LOGGER.info('Adding new devices: %s', devices[key])
|
||||||
if child.type not in s_types or value_type not in v_types:
|
add_devices([devices[key]])
|
||||||
continue
|
if key in devices:
|
||||||
|
devices[key].update_ha_state(True)
|
||||||
devices[node_id].append(
|
|
||||||
entity_class(gateway, node_id, child.id, name, value_type))
|
|
||||||
if devices[node_id]:
|
|
||||||
_LOGGER.info('adding new devices: %s', devices[node_id])
|
|
||||||
add_devices(devices[node_id])
|
|
||||||
for entity in devices[node_id]:
|
|
||||||
entity.update_ha_state(True)
|
|
||||||
return mysensors_callback
|
return mysensors_callback
|
||||||
|
|
||||||
|
|
||||||
|
37
homeassistant/components/nest.py
Normal file
37
homeassistant/components/nest.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.thermostat.nest
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Adds support for Nest thermostats.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/thermostat.nest/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['python-nest==2.6.0']
|
||||||
|
DOMAIN = 'nest'
|
||||||
|
|
||||||
|
NEST = None
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Sets up the nest thermostat. """
|
||||||
|
global NEST
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
username = config[DOMAIN].get(CONF_USERNAME)
|
||||||
|
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||||
|
|
||||||
|
if username is None or password is None:
|
||||||
|
logger.error("Missing required configuration items %s or %s",
|
||||||
|
CONF_USERNAME, CONF_PASSWORD)
|
||||||
|
return
|
||||||
|
|
||||||
|
import nest
|
||||||
|
|
||||||
|
NEST = nest.Nest(username, password)
|
||||||
|
|
||||||
|
return True
|
61
homeassistant/components/notify/googlevoice.py
Normal file
61
homeassistant/components/notify/googlevoice.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.googlevoice
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Google Voice SMS platform for notify component.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/notify.free_mobile/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, ATTR_TARGET, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
REQUIREMENTS = ['https://github.com/w1ll1am23/pygooglevoice-sms/archive/'
|
||||||
|
'7c5ee9969b97a7992fc86a753fe9f20e3ffa3f7c.zip#'
|
||||||
|
'pygooglevoice-sms==0.0.1']
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the Google Voice SMS notification service. """
|
||||||
|
|
||||||
|
if not validate_config({DOMAIN: config},
|
||||||
|
{DOMAIN: [CONF_USERNAME,
|
||||||
|
CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return GoogleVoiceSMSNotificationService(config[CONF_USERNAME],
|
||||||
|
config[CONF_PASSWORD])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class GoogleVoiceSMSNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for the Google Voice SMS service. """
|
||||||
|
|
||||||
|
def __init__(self, username, password):
|
||||||
|
from googlevoicesms import Voice
|
||||||
|
self.voice = Voice()
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Send SMS to specified target user cell. """
|
||||||
|
|
||||||
|
targets = kwargs.get(ATTR_TARGET)
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
_LOGGER.info('At least 1 target is required')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(targets, list):
|
||||||
|
targets = [targets]
|
||||||
|
|
||||||
|
self.voice.login(self.username, self.password)
|
||||||
|
|
||||||
|
for target in targets:
|
||||||
|
self.voice.send_sms(target, message)
|
||||||
|
|
||||||
|
self.voice.logout()
|
@ -21,37 +21,38 @@ def get_service(hass, config):
|
|||||||
""" Get the mail notification service. """
|
""" Get the mail notification service. """
|
||||||
|
|
||||||
if not validate_config({DOMAIN: config},
|
if not validate_config({DOMAIN: config},
|
||||||
{DOMAIN: ['server', 'port', 'sender', 'username',
|
{DOMAIN: ['recipient']},
|
||||||
'password', 'recipient']},
|
|
||||||
_LOGGER):
|
_LOGGER):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
smtp_server = config['server']
|
smtp_server = config.get('server', 'localhost')
|
||||||
port = int(config['port'])
|
port = int(config.get('port', '25'))
|
||||||
username = config['username']
|
username = config.get('username', None)
|
||||||
password = config['password']
|
password = config.get('password', None)
|
||||||
starttls = int(config['starttls'])
|
starttls = int(config.get('starttls', 0))
|
||||||
|
debug = config.get('debug', 0)
|
||||||
|
|
||||||
server = None
|
server = None
|
||||||
try:
|
try:
|
||||||
server = smtplib.SMTP(smtp_server, port)
|
server = smtplib.SMTP(smtp_server, port, timeout=5)
|
||||||
|
server.set_debuglevel(debug)
|
||||||
server.ehlo()
|
server.ehlo()
|
||||||
if starttls == 1:
|
if starttls == 1:
|
||||||
server.starttls()
|
server.starttls()
|
||||||
server.ehlo()
|
server.ehlo()
|
||||||
|
if username and password:
|
||||||
try:
|
try:
|
||||||
server.login(username, password)
|
server.login(username, password)
|
||||||
|
|
||||||
except (smtplib.SMTPException, smtplib.SMTPSenderRefused):
|
except (smtplib.SMTPException, smtplib.SMTPSenderRefused):
|
||||||
_LOGGER.exception("Please check your settings.")
|
_LOGGER.exception("Please check your settings.")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except smtplib.socket.gaierror:
|
except smtplib.socket.gaierror:
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"SMTP server not found. "
|
"SMTP server not found (%s:%s). "
|
||||||
"Please check the IP address or hostname of your SMTP server.")
|
"Please check the IP address or hostname of your SMTP server.",
|
||||||
|
smtp_server, port)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ def get_service(hass, config):
|
|||||||
|
|
||||||
return MailNotificationService(
|
return MailNotificationService(
|
||||||
smtp_server, port, config['sender'], starttls, username, password,
|
smtp_server, port, config['sender'], starttls, username, password,
|
||||||
config['recipient'])
|
config['recipient'], debug)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods, too-many-instance-attributes
|
# pylint: disable=too-few-public-methods, too-many-instance-attributes
|
||||||
@ -77,7 +78,7 @@ class MailNotificationService(BaseNotificationService):
|
|||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, server, port, sender, starttls, username,
|
def __init__(self, server, port, sender, starttls, username,
|
||||||
password, recipient):
|
password, recipient, debug):
|
||||||
self._server = server
|
self._server = server
|
||||||
self._port = port
|
self._port = port
|
||||||
self._sender = sender
|
self._sender = sender
|
||||||
@ -85,24 +86,26 @@ class MailNotificationService(BaseNotificationService):
|
|||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.recipient = recipient
|
self.recipient = recipient
|
||||||
|
self.debug = debug
|
||||||
self.tries = 2
|
self.tries = 2
|
||||||
self.mail = None
|
|
||||||
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
""" Connect/Authenticate to SMTP Server """
|
""" Connect/Authenticate to SMTP Server """
|
||||||
|
|
||||||
self.mail = smtplib.SMTP(self._server, self._port)
|
mail = smtplib.SMTP(self._server, self._port, timeout=5)
|
||||||
self.mail.ehlo_or_helo_if_needed()
|
mail.set_debuglevel(self.debug)
|
||||||
|
mail.ehlo_or_helo_if_needed()
|
||||||
if self.starttls == 1:
|
if self.starttls == 1:
|
||||||
self.mail.starttls()
|
mail.starttls()
|
||||||
self.mail.ehlo()
|
mail.ehlo()
|
||||||
self.mail.login(self.username, self.password)
|
if self.username and self.password:
|
||||||
|
mail.login(self.username, self.password)
|
||||||
|
return mail
|
||||||
|
|
||||||
def send_message(self, message="", **kwargs):
|
def send_message(self, message="", **kwargs):
|
||||||
""" Send a message to a user. """
|
""" Send a message to a user. """
|
||||||
|
|
||||||
|
mail = self.connect()
|
||||||
subject = kwargs.get(ATTR_TITLE)
|
subject = kwargs.get(ATTR_TITLE)
|
||||||
|
|
||||||
msg = MIMEText(message)
|
msg = MIMEText(message)
|
||||||
@ -113,10 +116,13 @@ class MailNotificationService(BaseNotificationService):
|
|||||||
|
|
||||||
for _ in range(self.tries):
|
for _ in range(self.tries):
|
||||||
try:
|
try:
|
||||||
self.mail.sendmail(self._sender, self.recipient,
|
mail.sendmail(self._sender, self.recipient,
|
||||||
msg.as_string())
|
msg.as_string())
|
||||||
break
|
break
|
||||||
except smtplib.SMTPException:
|
except smtplib.SMTPException:
|
||||||
_LOGGER.warning('SMTPException sending mail: '
|
_LOGGER.warning('SMTPException sending mail: '
|
||||||
'retrying connection')
|
'retrying connection')
|
||||||
self.connect()
|
mail.quit()
|
||||||
|
mail = self.connect()
|
||||||
|
|
||||||
|
mail.quit()
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.const import CONF_API_KEY
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = ['python-telegram-bot==2.8.7']
|
REQUIREMENTS = ['python-telegram-bot==3.2.0']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
59
homeassistant/components/notify/twitter.py
Normal file
59
homeassistant/components/notify/twitter.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.notify.twitter
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Twitter platform for notify component.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/notify.twitter/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.components.notify import (
|
||||||
|
DOMAIN, BaseNotificationService)
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
REQUIREMENTS = ['TwitterAPI==2.3.6']
|
||||||
|
|
||||||
|
CONF_CONSUMER_KEY = "consumer_key"
|
||||||
|
CONF_CONSUMER_SECRET = "consumer_secret"
|
||||||
|
CONF_ACCESS_TOKEN_SECRET = "access_token_secret"
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config):
|
||||||
|
""" Get the Twitter notification service. """
|
||||||
|
|
||||||
|
if not validate_config({DOMAIN: config},
|
||||||
|
{DOMAIN: [CONF_CONSUMER_KEY, CONF_CONSUMER_SECRET,
|
||||||
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_ACCESS_TOKEN_SECRET]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return TwitterNotificationService(config[CONF_CONSUMER_KEY],
|
||||||
|
config[CONF_CONSUMER_SECRET],
|
||||||
|
config[CONF_ACCESS_TOKEN],
|
||||||
|
config[CONF_ACCESS_TOKEN_SECRET])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class TwitterNotificationService(BaseNotificationService):
|
||||||
|
""" Implements notification service for the Twitter service. """
|
||||||
|
|
||||||
|
def __init__(self, consumer_key, consumer_secret, access_token_key,
|
||||||
|
access_token_secret):
|
||||||
|
from TwitterAPI import TwitterAPI
|
||||||
|
self.api = TwitterAPI(consumer_key, consumer_secret, access_token_key,
|
||||||
|
access_token_secret)
|
||||||
|
|
||||||
|
def send_message(self, message="", **kwargs):
|
||||||
|
""" Tweet some message. """
|
||||||
|
resp = self.api.request('statuses/update', {'status': message})
|
||||||
|
if resp.status_code != 200:
|
||||||
|
import json
|
||||||
|
obj = json.loads(resp.text)
|
||||||
|
error_message = obj['errors'][0]['message']
|
||||||
|
error_code = obj['errors'][0]['code']
|
||||||
|
_LOGGER.error("Error %s : %s (Code %s)", resp.status_code,
|
||||||
|
error_message,
|
||||||
|
error_code)
|
@ -48,11 +48,7 @@ def setup(hass, config):
|
|||||||
subscriber(event)
|
subscriber(event)
|
||||||
|
|
||||||
# Try to load the RFXtrx module
|
# Try to load the RFXtrx module
|
||||||
try:
|
|
||||||
import RFXtrx as rfxtrxmod
|
import RFXtrx as rfxtrxmod
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception("Failed to import rfxtrx")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Init the rfxtrx module
|
# Init the rfxtrx module
|
||||||
global RFXOBJECT
|
global RFXOBJECT
|
||||||
@ -74,11 +70,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def get_rfx_object(packetid):
|
def get_rfx_object(packetid):
|
||||||
""" Return the RFXObject with the packetid. """
|
""" Return the RFXObject with the packetid. """
|
||||||
try:
|
|
||||||
import RFXtrx as rfxtrxmod
|
import RFXtrx as rfxtrxmod
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception("Failed to import rfxtrx")
|
|
||||||
return False
|
|
||||||
|
|
||||||
binarypacket = bytearray.fromhex(packetid)
|
binarypacket = bytearray.fromhex(packetid)
|
||||||
|
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.rpi_gpio
|
homeassistant.components.rpi_gpio
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Allows to control the GPIO pins of a Raspberry Pi.
|
Allows to control the GPIO pins of a Raspberry Pi.
|
||||||
|
|
||||||
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/rpi_gpio/
|
https://home-assistant.io/components/rpi_gpio/
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=import-error
|
||||||
import logging
|
import logging
|
||||||
try:
|
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
except ImportError:
|
|
||||||
GPIO = None
|
|
||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
REQUIREMENTS = ['RPi.GPIO==0.6.1']
|
REQUIREMENTS = ['RPi.GPIO==0.6.1']
|
||||||
@ -22,9 +18,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up the Raspberry PI GPIO component. """
|
""" Sets up the Raspberry PI GPIO component. """
|
||||||
if GPIO is None:
|
import RPi.GPIO as GPIO
|
||||||
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def cleanup_gpio(event):
|
def cleanup_gpio(event):
|
||||||
""" Stuff to do before stop home assistant. """
|
""" Stuff to do before stop home assistant. """
|
||||||
@ -41,27 +35,32 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def setup_output(port):
|
def setup_output(port):
|
||||||
""" Setup a GPIO as output. """
|
""" Setup a GPIO as output. """
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
GPIO.setup(port, GPIO.OUT)
|
GPIO.setup(port, GPIO.OUT)
|
||||||
|
|
||||||
|
|
||||||
def setup_input(port, pull_mode):
|
def setup_input(port, pull_mode):
|
||||||
""" Setup a GPIO as input. """
|
""" Setup a GPIO as input. """
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
GPIO.setup(port, GPIO.IN,
|
GPIO.setup(port, GPIO.IN,
|
||||||
GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP)
|
GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP)
|
||||||
|
|
||||||
|
|
||||||
def write_output(port, value):
|
def write_output(port, value):
|
||||||
""" Write a value to a GPIO. """
|
""" Write a value to a GPIO. """
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
GPIO.output(port, value)
|
GPIO.output(port, value)
|
||||||
|
|
||||||
|
|
||||||
def read_input(port):
|
def read_input(port):
|
||||||
""" Read a value from a GPIO. """
|
""" Read a value from a GPIO. """
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
return GPIO.input(port)
|
return GPIO.input(port)
|
||||||
|
|
||||||
|
|
||||||
def edge_detect(port, event_callback, bounce):
|
def edge_detect(port, event_callback, bounce):
|
||||||
""" Adds detection for RISING and FALLING events. """
|
""" Adds detection for RISING and FALLING events. """
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
GPIO.add_event_detect(
|
GPIO.add_event_detect(
|
||||||
port,
|
port,
|
||||||
GPIO.BOTH,
|
GPIO.BOTH,
|
||||||
|
@ -13,9 +13,10 @@ from itertools import islice
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity, split_entity_id
|
||||||
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.helpers.service import call_from_config
|
||||||
|
from homeassistant.util import slugify
|
||||||
import homeassistant.util.dt as date_util
|
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,
|
||||||
@ -30,7 +31,8 @@ STATE_NOT_RUNNING = 'Not Running'
|
|||||||
CONF_ALIAS = "alias"
|
CONF_ALIAS = "alias"
|
||||||
CONF_SERVICE = "service"
|
CONF_SERVICE = "service"
|
||||||
CONF_SERVICE_OLD = "execute_service"
|
CONF_SERVICE_OLD = "execute_service"
|
||||||
CONF_SERVICE_DATA = "service_data"
|
CONF_SERVICE_DATA = "data"
|
||||||
|
CONF_SERVICE_DATA_OLD = "service_data"
|
||||||
CONF_SEQUENCE = "sequence"
|
CONF_SEQUENCE = "sequence"
|
||||||
CONF_EVENT = "event"
|
CONF_EVENT = "event"
|
||||||
CONF_EVENT_DATA = "event_data"
|
CONF_EVENT_DATA = "event_data"
|
||||||
@ -194,13 +196,17 @@ class Script(ToggleEntity):
|
|||||||
|
|
||||||
def _call_service(self, action):
|
def _call_service(self, action):
|
||||||
""" Calls the service specified in the action. """
|
""" Calls the service specified in the action. """
|
||||||
conf_service = action.get(CONF_SERVICE, action.get(CONF_SERVICE_OLD))
|
# Backwards compatibility
|
||||||
self._last_action = action.get(CONF_ALIAS, conf_service)
|
if CONF_SERVICE not in action and CONF_SERVICE_OLD in action:
|
||||||
|
action[CONF_SERVICE] = action[CONF_SERVICE_OLD]
|
||||||
|
|
||||||
|
if CONF_SERVICE_DATA not in action and CONF_SERVICE_DATA_OLD in action:
|
||||||
|
action[CONF_SERVICE_DATA] = action[CONF_SERVICE_DATA_OLD]
|
||||||
|
|
||||||
|
self._last_action = action.get(CONF_ALIAS, action[CONF_SERVICE])
|
||||||
_LOGGER.info("Executing script %s step %s", self._name,
|
_LOGGER.info("Executing script %s step %s", self._name,
|
||||||
self._last_action)
|
self._last_action)
|
||||||
domain, service = split_entity_id(conf_service)
|
call_from_config(self.hass, action, True)
|
||||||
data = action.get(CONF_SERVICE_DATA, {})
|
|
||||||
self.hass.services.call(domain, service, data, True)
|
|
||||||
|
|
||||||
def _fire_event(self, action):
|
def _fire_event(self, action):
|
||||||
""" Fires an event. """
|
""" Fires an event. """
|
||||||
|
@ -13,7 +13,7 @@ from homeassistant.util import Throttle
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['blockchain==1.1.2']
|
REQUIREMENTS = ['blockchain==1.2.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
OPTION_TYPES = {
|
OPTION_TYPES = {
|
||||||
'wallet': ['Wallet balance', 'BTC'],
|
'wallet': ['Wallet balance', 'BTC'],
|
||||||
@ -47,17 +47,9 @@ 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 Bitcoin sensor. """
|
""" Get the Bitcoin sensor. """
|
||||||
|
|
||||||
try:
|
|
||||||
from blockchain.wallet import Wallet
|
from blockchain.wallet import Wallet
|
||||||
from blockchain import exchangerates, exceptions
|
from blockchain import exchangerates, exceptions
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unable to import blockchain. "
|
|
||||||
"Did you maybe not install the 'blockchain' package?")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
wallet_id = config.get('wallet', None)
|
wallet_id = config.get('wallet', None)
|
||||||
password = config.get('password', None)
|
password = config.get('password', None)
|
||||||
currency = config.get('currency', 'USD')
|
currency = config.get('currency', 'USD')
|
||||||
|
@ -10,7 +10,7 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['py-cpuinfo==0.1.6']
|
REQUIREMENTS = ['py-cpuinfo==0.1.8']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -25,14 +25,6 @@ ATTR_HZ = 'GHz Advertised'
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the CPU speed sensor. """
|
""" Sets up the CPU speed sensor. """
|
||||||
|
|
||||||
try:
|
|
||||||
import cpuinfo # noqa
|
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unable to import cpuinfo. "
|
|
||||||
"Did you maybe not install the 'py-cpuinfo' package?")
|
|
||||||
return False
|
|
||||||
|
|
||||||
add_devices([CpuSpeedSensor(config.get('name', DEFAULT_NAME))])
|
add_devices([CpuSpeedSensor(config.get('name', DEFAULT_NAME))])
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,16 +31,9 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Get the DHT sensor. """
|
""" Get the DHT sensor. """
|
||||||
|
|
||||||
try:
|
# pylint: disable=import-error
|
||||||
import Adafruit_DHT
|
import Adafruit_DHT
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unable to import Adafruit_DHT. "
|
|
||||||
"Did you maybe not install the 'Adafruit_DHT' package?")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
||||||
unit = hass.config.temperature_unit
|
unit = hass.config.temperature_unit
|
||||||
available_sensors = {
|
available_sensors = {
|
||||||
|
@ -7,13 +7,12 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/sensor.mysensors/
|
https://home-assistant.io/components/sensor.mysensors/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT,
|
TEMP_CELCIUS,
|
||||||
STATE_ON, STATE_OFF)
|
STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
import homeassistant.components.mysensors as mysensors
|
import homeassistant.components.mysensors as mysensors
|
||||||
@ -31,50 +30,57 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
for gateway in mysensors.GATEWAYS.values():
|
for gateway in mysensors.GATEWAYS.values():
|
||||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||||
# states.
|
# states. Map them in a defaultdict(list).
|
||||||
s_types = [
|
pres = gateway.const.Presentation
|
||||||
gateway.const.Presentation.S_DOOR,
|
set_req = gateway.const.SetReq
|
||||||
gateway.const.Presentation.S_MOTION,
|
map_sv_types = {
|
||||||
gateway.const.Presentation.S_SMOKE,
|
pres.S_DOOR: [set_req.V_TRIPPED],
|
||||||
gateway.const.Presentation.S_TEMP,
|
pres.S_MOTION: [set_req.V_TRIPPED],
|
||||||
gateway.const.Presentation.S_HUM,
|
pres.S_SMOKE: [set_req.V_TRIPPED],
|
||||||
gateway.const.Presentation.S_BARO,
|
pres.S_TEMP: [set_req.V_TEMP],
|
||||||
gateway.const.Presentation.S_WIND,
|
pres.S_HUM: [set_req.V_HUM],
|
||||||
gateway.const.Presentation.S_RAIN,
|
pres.S_BARO: [set_req.V_PRESSURE, set_req.V_FORECAST],
|
||||||
gateway.const.Presentation.S_UV,
|
pres.S_WIND: [set_req.V_WIND, set_req.V_GUST],
|
||||||
gateway.const.Presentation.S_WEIGHT,
|
pres.S_RAIN: [set_req.V_RAIN, set_req.V_RAINRATE],
|
||||||
gateway.const.Presentation.S_POWER,
|
pres.S_UV: [set_req.V_UV],
|
||||||
gateway.const.Presentation.S_DISTANCE,
|
pres.S_WEIGHT: [set_req.V_WEIGHT, set_req.V_IMPEDANCE],
|
||||||
gateway.const.Presentation.S_LIGHT_LEVEL,
|
pres.S_POWER: [set_req.V_WATT, set_req.V_KWH],
|
||||||
gateway.const.Presentation.S_IR,
|
pres.S_DISTANCE: [set_req.V_DISTANCE],
|
||||||
gateway.const.Presentation.S_WATER,
|
pres.S_LIGHT_LEVEL: [set_req.V_LIGHT_LEVEL],
|
||||||
gateway.const.Presentation.S_AIR_QUALITY,
|
pres.S_IR: [set_req.V_IR_SEND, set_req.V_IR_RECEIVE],
|
||||||
gateway.const.Presentation.S_CUSTOM,
|
pres.S_WATER: [set_req.V_FLOW, set_req.V_VOLUME],
|
||||||
gateway.const.Presentation.S_DUST,
|
pres.S_CUSTOM: [set_req.V_VAR1,
|
||||||
gateway.const.Presentation.S_SCENE_CONTROLLER,
|
set_req.V_VAR2,
|
||||||
]
|
set_req.V_VAR3,
|
||||||
not_v_types = [
|
set_req.V_VAR4,
|
||||||
gateway.const.SetReq.V_ARMED,
|
set_req.V_VAR5],
|
||||||
gateway.const.SetReq.V_LIGHT,
|
pres.S_SCENE_CONTROLLER: [set_req.V_SCENE_ON,
|
||||||
gateway.const.SetReq.V_LOCK_STATUS,
|
set_req.V_SCENE_OFF],
|
||||||
]
|
}
|
||||||
|
if float(gateway.version) < 1.5:
|
||||||
|
map_sv_types.update({
|
||||||
|
pres.S_AIR_QUALITY: [set_req.V_DUST_LEVEL],
|
||||||
|
pres.S_DUST: [set_req.V_DUST_LEVEL],
|
||||||
|
})
|
||||||
if float(gateway.version) >= 1.5:
|
if float(gateway.version) >= 1.5:
|
||||||
s_types.extend([
|
map_sv_types.update({
|
||||||
gateway.const.Presentation.S_COLOR_SENSOR,
|
pres.S_COLOR_SENSOR: [set_req.V_RGB],
|
||||||
gateway.const.Presentation.S_MULTIMETER,
|
pres.S_MULTIMETER: [set_req.V_VOLTAGE,
|
||||||
gateway.const.Presentation.S_SPRINKLER,
|
set_req.V_CURRENT,
|
||||||
gateway.const.Presentation.S_WATER_LEAK,
|
set_req.V_IMPEDANCE],
|
||||||
gateway.const.Presentation.S_SOUND,
|
pres.S_SPRINKLER: [set_req.V_TRIPPED],
|
||||||
gateway.const.Presentation.S_VIBRATION,
|
pres.S_WATER_LEAK: [set_req.V_TRIPPED],
|
||||||
gateway.const.Presentation.S_MOISTURE,
|
pres.S_SOUND: [set_req.V_TRIPPED, set_req.V_LEVEL],
|
||||||
])
|
pres.S_VIBRATION: [set_req.V_TRIPPED, set_req.V_LEVEL],
|
||||||
not_v_types.extend([gateway.const.SetReq.V_STATUS, ])
|
pres.S_MOISTURE: [set_req.V_TRIPPED, set_req.V_LEVEL],
|
||||||
v_types = [member for member in gateway.const.SetReq
|
pres.S_AIR_QUALITY: [set_req.V_LEVEL],
|
||||||
if member.value not in not_v_types]
|
pres.S_DUST: [set_req.V_LEVEL],
|
||||||
|
})
|
||||||
|
map_sv_types[pres.S_LIGHT_LEVEL].append(set_req.V_LEVEL)
|
||||||
|
|
||||||
devices = defaultdict(list)
|
devices = {}
|
||||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||||
s_types, v_types, devices, add_devices, MySensorsSensor))
|
map_sv_types, devices, add_devices, MySensorsSensor))
|
||||||
|
|
||||||
|
|
||||||
class MySensorsSensor(Entity):
|
class MySensorsSensor(Entity):
|
||||||
@ -129,33 +135,39 @@ class MySensorsSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Unit of measurement of this entity."""
|
"""Unit of measurement of this entity."""
|
||||||
# pylint:disable=too-many-return-statements
|
# HA will convert to degrees F if needed
|
||||||
if self.value_type == self.gateway.const.SetReq.V_TEMP:
|
unit_map = {
|
||||||
return TEMP_CELCIUS if self.gateway.metric else TEMP_FAHRENHEIT
|
self.gateway.const.SetReq.V_TEMP: TEMP_CELCIUS,
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_HUM or \
|
self.gateway.const.SetReq.V_HUM: '%',
|
||||||
self.value_type == self.gateway.const.SetReq.V_DIMMER or \
|
self.gateway.const.SetReq.V_DIMMER: '%',
|
||||||
self.value_type == self.gateway.const.SetReq.V_PERCENTAGE or \
|
self.gateway.const.SetReq.V_LIGHT_LEVEL: '%',
|
||||||
self.value_type == self.gateway.const.SetReq.V_LIGHT_LEVEL:
|
self.gateway.const.SetReq.V_WEIGHT: 'kg',
|
||||||
return '%'
|
self.gateway.const.SetReq.V_DISTANCE: 'm',
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_WATT:
|
self.gateway.const.SetReq.V_IMPEDANCE: 'ohm',
|
||||||
return 'W'
|
self.gateway.const.SetReq.V_WATT: 'W',
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_KWH:
|
self.gateway.const.SetReq.V_KWH: 'kWh',
|
||||||
return 'kWh'
|
self.gateway.const.SetReq.V_FLOW: 'm',
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_VOLTAGE:
|
self.gateway.const.SetReq.V_VOLUME: 'm3',
|
||||||
return 'V'
|
self.gateway.const.SetReq.V_VOLTAGE: 'V',
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_CURRENT:
|
self.gateway.const.SetReq.V_CURRENT: 'A',
|
||||||
return 'A'
|
}
|
||||||
elif self.value_type == self.gateway.const.SetReq.V_IMPEDANCE:
|
unit_map_v15 = {
|
||||||
return 'ohm'
|
self.gateway.const.SetReq.V_PERCENTAGE: '%',
|
||||||
elif self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
|
}
|
||||||
return self._values[self.gateway.const.SetReq.V_UNIT_PREFIX]
|
if float(self.gateway.version) >= 1.5:
|
||||||
return None
|
if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
|
||||||
|
return self._values[
|
||||||
|
self.gateway.const.SetReq.V_UNIT_PREFIX]
|
||||||
|
unit_map.update(unit_map_v15)
|
||||||
|
return unit_map.get(self.value_type)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
device_attr = dict(self._values)
|
device_attr = {}
|
||||||
device_attr.pop(self.value_type, None)
|
for value_type, value in self._values.items():
|
||||||
|
if value_type != self.value_type:
|
||||||
|
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||||
return device_attr
|
return device_attr
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
111
homeassistant/components/sensor/nest.py
Normal file
111
homeassistant/components/sensor/nest.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.nest
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Nest Thermostat Sensors.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.nest/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import homeassistant.components.nest as nest
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
|
|
||||||
|
DEPENDENCIES = ['nest']
|
||||||
|
SENSOR_TYPES = ['humidity',
|
||||||
|
'mode',
|
||||||
|
'last_ip',
|
||||||
|
'local_ip',
|
||||||
|
'last_connection',
|
||||||
|
'battery_level']
|
||||||
|
|
||||||
|
SENSOR_UNITS = {'humidity': '%', 'battery_level': '%'}
|
||||||
|
|
||||||
|
SENSOR_TEMP_TYPES = ['temperature',
|
||||||
|
'target',
|
||||||
|
'away_temperature[0]',
|
||||||
|
'away_temperature[1]']
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Setup Nest Sensor. """
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
try:
|
||||||
|
for structure in nest.NEST.structures:
|
||||||
|
for device in structure.devices:
|
||||||
|
for variable in config['monitored_conditions']:
|
||||||
|
if variable in SENSOR_TYPES:
|
||||||
|
add_devices([NestBasicSensor(structure,
|
||||||
|
device,
|
||||||
|
variable)])
|
||||||
|
elif variable in SENSOR_TEMP_TYPES:
|
||||||
|
add_devices([NestTempSensor(structure,
|
||||||
|
device,
|
||||||
|
variable)])
|
||||||
|
else:
|
||||||
|
logger.error('Nest sensor type: "%s" does not exist',
|
||||||
|
variable)
|
||||||
|
except socket.error:
|
||||||
|
logger.error(
|
||||||
|
"Connection error logging into the nest web service."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NestSensor(Entity):
|
||||||
|
""" Represents a Nest sensor. """
|
||||||
|
|
||||||
|
def __init__(self, structure, device, variable):
|
||||||
|
self.structure = structure
|
||||||
|
self.device = device
|
||||||
|
self.variable = variable
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the nest, if any. """
|
||||||
|
|
||||||
|
location = self.device.where
|
||||||
|
name = self.device.name
|
||||||
|
if location is None:
|
||||||
|
return "{} {}".format(name, self.variable)
|
||||||
|
else:
|
||||||
|
if name == '':
|
||||||
|
return "{} {}".format(location.capitalize(), self.variable)
|
||||||
|
else:
|
||||||
|
return "{}({}){}".format(location.capitalize(),
|
||||||
|
name,
|
||||||
|
self.variable)
|
||||||
|
|
||||||
|
|
||||||
|
class NestBasicSensor(NestSensor):
|
||||||
|
""" Represents a basic Nest sensor with state. """
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the sensor. """
|
||||||
|
return getattr(self.device, self.variable)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit the value is expressed in. """
|
||||||
|
return SENSOR_UNITS.get(self.variable, None)
|
||||||
|
|
||||||
|
|
||||||
|
class NestTempSensor(NestSensor):
|
||||||
|
""" Represents a Nest Temperature sensor. """
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit the value is expressed in. """
|
||||||
|
return TEMP_CELCIUS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the sensor. """
|
||||||
|
temp = getattr(self.device, self.variable)
|
||||||
|
if temp is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return round(temp, 1)
|
@ -23,11 +23,11 @@ REQUIREMENTS = [
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'temperature': ['Temperature', TEMP_CELCIUS],
|
'temperature': ['Temperature', TEMP_CELCIUS, 'mdi:thermometer'],
|
||||||
'co2': ['CO2', 'ppm'],
|
'co2': ['CO2', 'ppm', 'mdi:cloud'],
|
||||||
'pressure': ['Pressure', 'mbar'],
|
'pressure': ['Pressure', 'mbar', 'mdi:gauge'],
|
||||||
'noise': ['Noise', 'dB'],
|
'noise': ['Noise', 'dB', 'mdi:volume-high'],
|
||||||
'humidity': ['Humidity', '%']
|
'humidity': ['Humidity', '%', 'mdi:water-percent']
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF_SECRET_KEY = 'secret_key'
|
CONF_SECRET_KEY = 'secret_key'
|
||||||
@ -104,6 +104,10 @@ class NetAtmoSensor(Entity):
|
|||||||
def name(self):
|
def name(self):
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
return SENSOR_TYPES[self.type][2]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
|
100
homeassistant/components/sensor/onewire.py
Normal file
100
homeassistant/components/sensor/onewire.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.onewire
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for DS18B20 One Wire Sensors.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.onewire/
|
||||||
|
"""
|
||||||
|
from glob import glob
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from homeassistant.const import TEMP_CELCIUS, STATE_UNKNOWN
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
BASE_DIR = '/sys/bus/w1/devices/'
|
||||||
|
DEVICE_FOLDERS = glob(os.path.join(BASE_DIR, '28*'))
|
||||||
|
SENSOR_IDS = []
|
||||||
|
DEVICE_FILES = []
|
||||||
|
for device_folder in DEVICE_FOLDERS:
|
||||||
|
SENSOR_IDS.append(os.path.split(device_folder)[1])
|
||||||
|
DEVICE_FILES.append(os.path.join(device_folder, 'w1_slave'))
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the one wire Sensors. """
|
||||||
|
|
||||||
|
if DEVICE_FILES == []:
|
||||||
|
_LOGGER.error('No onewire sensor found.')
|
||||||
|
_LOGGER.error('Check if dtoverlay=w1-gpio,gpiopin=4.')
|
||||||
|
_LOGGER.error('is in your /boot/config.txt and')
|
||||||
|
_LOGGER.error('the correct gpiopin number is set.')
|
||||||
|
return
|
||||||
|
|
||||||
|
devs = []
|
||||||
|
names = SENSOR_IDS
|
||||||
|
|
||||||
|
for key in config.keys():
|
||||||
|
if key == "names":
|
||||||
|
# only one name given
|
||||||
|
if isinstance(config['names'], str):
|
||||||
|
names = [config['names']]
|
||||||
|
# map names and sensors in given order
|
||||||
|
elif isinstance(config['names'], list):
|
||||||
|
names = config['names']
|
||||||
|
# map names to ids.
|
||||||
|
elif isinstance(config['names'], dict):
|
||||||
|
names = []
|
||||||
|
for sensor_id in SENSOR_IDS:
|
||||||
|
names.append(config['names'].get(sensor_id, sensor_id))
|
||||||
|
for device_file, name in zip(DEVICE_FILES, names):
|
||||||
|
devs.append(OneWire(name, device_file))
|
||||||
|
add_devices(devs)
|
||||||
|
|
||||||
|
|
||||||
|
class OneWire(Entity):
|
||||||
|
""" An One wire Sensor. """
|
||||||
|
|
||||||
|
def __init__(self, name, device_file):
|
||||||
|
self._name = name
|
||||||
|
self._device_file = device_file
|
||||||
|
self._state = STATE_UNKNOWN
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _read_temp_raw(self):
|
||||||
|
""" Read the temperature as it is returned by the sensor. """
|
||||||
|
ds_device_file = open(self._device_file, 'r')
|
||||||
|
lines = ds_device_file.readlines()
|
||||||
|
ds_device_file.close()
|
||||||
|
return lines
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit the value is expressed in. """
|
||||||
|
return TEMP_CELCIUS
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data from the device. """
|
||||||
|
lines = self._read_temp_raw()
|
||||||
|
while lines[0].strip()[-3:] != 'YES':
|
||||||
|
time.sleep(0.2)
|
||||||
|
lines = self._read_temp_raw()
|
||||||
|
equals_pos = lines[1].find('t=')
|
||||||
|
if equals_pos != -1:
|
||||||
|
temp_string = lines[1][equals_pos+2:]
|
||||||
|
temp = float(temp_string) / 1000.0
|
||||||
|
self._state = temp
|
@ -37,16 +37,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
|
||||||
from pyowm import OWM
|
from pyowm import OWM
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unable to import pyowm. "
|
|
||||||
"Did you maybe not install the 'PyOWM' package?")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
||||||
unit = hass.config.temperature_unit
|
unit = hass.config.temperature_unit
|
||||||
forecast = config.get('forecast', 0)
|
forecast = config.get('forecast', 0)
|
||||||
@ -165,6 +157,10 @@ class WeatherData(object):
|
|||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest data from OpenWeatherMap. """
|
""" Gets the latest data from OpenWeatherMap. """
|
||||||
obs = self.owm.weather_at_coords(self.latitude, self.longitude)
|
obs = self.owm.weather_at_coords(self.latitude, self.longitude)
|
||||||
|
if obs is None:
|
||||||
|
_LOGGER.warning('Failed to fetch data from OWM')
|
||||||
|
return
|
||||||
|
|
||||||
self.data = obs.get_weather()
|
self.data = obs.get_weather()
|
||||||
|
|
||||||
if self.forecast == 1:
|
if self.forecast == 1:
|
||||||
|
@ -12,7 +12,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
|
|
||||||
REQUIREMENTS = ['psutil==3.2.2']
|
REQUIREMENTS = ['psutil==3.4.2']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
|
||||||
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],
|
||||||
|
@ -20,12 +20,7 @@ REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/'
|
|||||||
# 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 Temper sensors. """
|
""" Find and return Temper sensors. """
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
from temperusb.temper import TemperHandler
|
from temperusb.temper import TemperHandler
|
||||||
except ImportError:
|
|
||||||
_LOGGER.error('Failed to import temperusb')
|
|
||||||
return False
|
|
||||||
|
|
||||||
temp_unit = hass.config.temperature_unit
|
temp_unit = hass.config.temperature_unit
|
||||||
name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME)
|
name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME)
|
||||||
|
111
homeassistant/components/sensor/template.py
Normal file
111
homeassistant/components/sensor/template.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.template
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows the creation of a sensor that breaks out state_attributes
|
||||||
|
from other entities.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.template/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.core import EVENT_STATE_CHANGED
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
|
||||||
|
from homeassistant.util import template
|
||||||
|
from homeassistant.exceptions import TemplateError
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
CONF_SENSORS = 'sensors'
|
||||||
|
STATE_ERROR = 'error'
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the sensors. """
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
if config.get(CONF_SENSORS) is None:
|
||||||
|
_LOGGER.error("Missing configuration data for sensor platform")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for device, device_config in config[CONF_SENSORS].items():
|
||||||
|
if not isinstance(device_config, dict):
|
||||||
|
_LOGGER.error("Missing configuration data for sensor %s", device)
|
||||||
|
continue
|
||||||
|
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||||
|
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
state_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
if state_template is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Missing %s for sensor %s", CONF_VALUE_TEMPLATE, device)
|
||||||
|
continue
|
||||||
|
sensors.append(
|
||||||
|
SensorTemplate(
|
||||||
|
hass,
|
||||||
|
friendly_name,
|
||||||
|
unit_of_measurement,
|
||||||
|
state_template)
|
||||||
|
)
|
||||||
|
if sensors is None:
|
||||||
|
_LOGGER.error("No sensors added")
|
||||||
|
return False
|
||||||
|
add_devices(sensors)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class SensorTemplate(Entity):
|
||||||
|
""" Represents a Template Sensor. """
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self,
|
||||||
|
hass,
|
||||||
|
friendly_name,
|
||||||
|
unit_of_measurement,
|
||||||
|
state_template):
|
||||||
|
|
||||||
|
self.hass = hass
|
||||||
|
self._name = friendly_name
|
||||||
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
self._template = state_template
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _update_callback(_event):
|
||||||
|
""" Called when the target device changes state. """
|
||||||
|
# This can be called before the entity is properly
|
||||||
|
# initialised, so check before updating state,
|
||||||
|
if self.entity_id:
|
||||||
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Returns the unit_of_measurement of the device. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" Tells Home Assistant not to poll this entity. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
try:
|
||||||
|
self._state = template.render(self.hass, self._template)
|
||||||
|
except TemplateError as ex:
|
||||||
|
self._state = STATE_ERROR
|
||||||
|
_LOGGER.error(ex)
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.helpers.entity import Entity
|
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 = ['python-wink==0.4.1']
|
REQUIREMENTS = ['python-wink==0.4.2']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -10,7 +10,9 @@ import logging
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
from homeassistant.const import (ATTR_ENTITY_PICTURE,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import location, dt as dt_util
|
from homeassistant.util import location, dt as dt_util
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ SENSOR_TYPES = {
|
|||||||
'precipitation': ['Condition', 'mm'],
|
'precipitation': ['Condition', 'mm'],
|
||||||
'temperature': ['Temperature', '°C'],
|
'temperature': ['Temperature', '°C'],
|
||||||
'windSpeed': ['Wind speed', 'm/s'],
|
'windSpeed': ['Wind speed', 'm/s'],
|
||||||
|
'windGust': ['Wind gust', 'm/s'],
|
||||||
'pressure': ['Pressure', 'mbar'],
|
'pressure': ['Pressure', 'mbar'],
|
||||||
'windDirection': ['Wind direction', '°'],
|
'windDirection': ['Wind direction', '°'],
|
||||||
'humidity': ['Humidity', '%'],
|
'humidity': ['Humidity', '%'],
|
||||||
@ -40,18 +43,21 @@ SENSOR_TYPES = {
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Get the Yr.no sensor. """
|
""" Get the Yr.no sensor. """
|
||||||
|
|
||||||
if None in (hass.config.latitude, hass.config.longitude):
|
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||||
|
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||||
|
elevation = config.get('elevation')
|
||||||
|
|
||||||
|
if None in (latitude, longitude):
|
||||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elevation = config.get('elevation')
|
|
||||||
|
|
||||||
if elevation is None:
|
if elevation is None:
|
||||||
elevation = location.elevation(hass.config.latitude,
|
elevation = location.elevation(latitude,
|
||||||
hass.config.longitude)
|
longitude)
|
||||||
|
|
||||||
coordinates = dict(lat=hass.config.latitude,
|
coordinates = dict(lat=latitude,
|
||||||
lon=hass.config.longitude, msl=elevation)
|
lon=longitude,
|
||||||
|
msl=elevation)
|
||||||
|
|
||||||
weather = YrData(coordinates)
|
weather = YrData(coordinates)
|
||||||
|
|
||||||
@ -143,11 +149,11 @@ class YrSensor(Entity):
|
|||||||
elif self.type == 'symbol' and valid_from < now:
|
elif self.type == 'symbol' and valid_from < now:
|
||||||
self._state = loc_data[self.type]['@number']
|
self._state = loc_data[self.type]['@number']
|
||||||
break
|
break
|
||||||
elif self.type == ('temperature', 'pressure', 'humidity',
|
elif self.type in ('temperature', 'pressure', 'humidity',
|
||||||
'dewpointTemperature'):
|
'dewpointTemperature'):
|
||||||
self._state = loc_data[self.type]['@value']
|
self._state = loc_data[self.type]['@value']
|
||||||
break
|
break
|
||||||
elif self.type == 'windSpeed':
|
elif self.type in ('windSpeed', 'windGust'):
|
||||||
self._state = loc_data[self.type]['@mps']
|
self._state = loc_data[self.type]['@mps']
|
||||||
break
|
break
|
||||||
elif self.type == 'windDirection':
|
elif self.type == 'windDirection':
|
||||||
|
77
homeassistant/components/sensor/zigbee.py
Normal file
77
homeassistant/components/sensor/zigbee.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.zigbee
|
||||||
|
|
||||||
|
Contains functionality to use a ZigBee device as a sensor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from binascii import hexlify
|
||||||
|
|
||||||
|
from homeassistant.core import JobPriority
|
||||||
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.components import zigbee
|
||||||
|
|
||||||
|
|
||||||
|
DEPENDENCIES = ["zigbee"]
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""
|
||||||
|
Uses the 'type' config value to work out which type of ZigBee sensor we're
|
||||||
|
dealing with and instantiates the relevant classes to handle it.
|
||||||
|
"""
|
||||||
|
typ = config.get("type", "").lower()
|
||||||
|
if not typ:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Must include 'type' when configuring a ZigBee sensor.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
sensor_class, config_class = TYPE_CLASSES[typ]
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.exception("Unknown ZigBee sensor type: %s", typ)
|
||||||
|
return
|
||||||
|
add_entities([sensor_class(hass, config_class(config))])
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeTemperatureSensor(Entity):
|
||||||
|
"""
|
||||||
|
Allows usage of an XBee Pro as a temperature sensor.
|
||||||
|
"""
|
||||||
|
def __init__(self, hass, config):
|
||||||
|
self._config = config
|
||||||
|
self._temp = None
|
||||||
|
# Get initial state
|
||||||
|
hass.pool.add_job(
|
||||||
|
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._config.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
return TEMP_CELCIUS
|
||||||
|
|
||||||
|
def update(self, *args):
|
||||||
|
try:
|
||||||
|
self._temp = zigbee.DEVICE.get_temperature(self._config.address)
|
||||||
|
except zigbee.ZIGBEE_TX_FAILURE:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Transmission failure when attempting to get sample from "
|
||||||
|
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||||
|
except zigbee.ZIGBEE_EXCEPTION as exc:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to get sample from ZigBee device: %s", exc)
|
||||||
|
|
||||||
|
|
||||||
|
# This must be below the classes to which it refers.
|
||||||
|
TYPE_CLASSES = {
|
||||||
|
"temperature": (ZigBeeTemperatureSensor, zigbee.ZigBeeConfig),
|
||||||
|
"analog": (zigbee.ZigBeeAnalogIn, zigbee.ZigBeeAnalogInConfig)
|
||||||
|
}
|
@ -12,11 +12,15 @@ import datetime
|
|||||||
|
|
||||||
from homeassistant.helpers.event import track_point_in_time
|
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
|
from homeassistant.components.sensor import DOMAIN
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.components.zwave import (
|
||||||
|
NETWORK, ATTR_NODE_ID, ATTR_VALUE_ID, COMMAND_CLASS_SENSOR_BINARY,
|
||||||
|
COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, TYPE_DECIMAL,
|
||||||
|
COMMAND_CLASS_ALARM, ZWaveDeviceEntity, get_config_value)
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL, STATE_ON, STATE_OFF,
|
STATE_ON, STATE_OFF, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION)
|
|
||||||
|
|
||||||
PHILIO = '013c'
|
PHILIO = '013c'
|
||||||
PHILIO_SLIM_SENSOR = '0002'
|
PHILIO_SLIM_SENSOR = '0002'
|
||||||
@ -41,14 +45,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
|
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||||
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
|
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
|
|
||||||
# if 1 in groups and (zwave.NETWORK.controller.node_id not in
|
# if 1 in groups and (NETWORK.controller.node_id not in
|
||||||
# groups[1].associations):
|
# groups[1].associations):
|
||||||
# node.groups[1].add_association(zwave.NETWORK.controller.node_id)
|
# node.groups[1].add_association(NETWORK.controller.node_id)
|
||||||
|
|
||||||
specific_sensor_key = (value.node.manufacturer_id,
|
specific_sensor_key = (value.node.manufacturer_id,
|
||||||
value.node.product_id,
|
value.node.product_id,
|
||||||
@ -58,81 +62,43 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if specific_sensor_key in DEVICE_MAPPINGS:
|
if specific_sensor_key in DEVICE_MAPPINGS:
|
||||||
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
|
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
|
||||||
# Default the multiplier to 4
|
# Default the multiplier to 4
|
||||||
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4)
|
re_arm_multiplier = (get_config_value(value.node, 9) or 4)
|
||||||
add_devices([
|
add_devices([
|
||||||
ZWaveTriggerSensor(value, hass, re_arm_multiplier * 8)
|
ZWaveTriggerSensor(value, hass, re_arm_multiplier * 8)
|
||||||
])
|
])
|
||||||
|
|
||||||
# generic Device mappings
|
# generic Device mappings
|
||||||
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
|
elif value.command_class == COMMAND_CLASS_SENSOR_BINARY:
|
||||||
add_devices([ZWaveBinarySensor(value)])
|
add_devices([ZWaveBinarySensor(value)])
|
||||||
|
|
||||||
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
|
elif value.command_class == COMMAND_CLASS_SENSOR_MULTILEVEL:
|
||||||
add_devices([ZWaveMultilevelSensor(value)])
|
add_devices([ZWaveMultilevelSensor(value)])
|
||||||
|
|
||||||
elif (value.command_class == zwave.COMMAND_CLASS_METER and
|
elif (value.command_class == COMMAND_CLASS_METER and
|
||||||
value.type == zwave.TYPE_DECIMAL):
|
value.type == TYPE_DECIMAL):
|
||||||
add_devices([ZWaveMultilevelSensor(value)])
|
add_devices([ZWaveMultilevelSensor(value)])
|
||||||
|
|
||||||
elif value.command_class == zwave.COMMAND_CLASS_ALARM:
|
elif value.command_class == COMMAND_CLASS_ALARM:
|
||||||
add_devices([ZWaveAlarmSensor(value)])
|
add_devices([ZWaveAlarmSensor(value)])
|
||||||
|
|
||||||
|
|
||||||
class ZWaveSensor(Entity):
|
class ZWaveSensor(ZWaveDeviceEntity, 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 openzwave.network import ZWaveNetwork
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
self._value = sensor_value
|
ZWaveDeviceEntity.__init__(self, sensor_value, DOMAIN)
|
||||||
self._node = sensor_value.node
|
|
||||||
|
|
||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
""" False because we will push our own state to HA when changed. """
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
""" Returns a unique id. """
|
|
||||||
return "ZWAVE-{}-{}".format(self._node.node_id, self._value.object_id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
""" Returns the name of the device. """
|
|
||||||
name = self._node.name or "{} {}".format(
|
|
||||||
self._node.manufacturer_name, self._node.product_name)
|
|
||||||
|
|
||||||
return "{} {}".format(name, self._value.label)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the sensor. """
|
""" Returns the state of the sensor. """
|
||||||
return self._value.data
|
return self._value.data
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
""" Returns the state attributes. """
|
|
||||||
attrs = {
|
|
||||||
zwave.ATTR_NODE_ID: self._node.node_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
battery_level = self._node.get_battery_level()
|
|
||||||
|
|
||||||
if battery_level is not None:
|
|
||||||
attrs[ATTR_BATTERY_LEVEL] = battery_level
|
|
||||||
|
|
||||||
location = self._node.location
|
|
||||||
|
|
||||||
if location:
|
|
||||||
attrs[ATTR_LOCATION] = location
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
return self._value.units
|
return self._value.units
|
||||||
|
86
homeassistant/components/statsd.py
Normal file
86
homeassistant/components/statsd.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.statsd
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
StatsD component which allows you to send data to many backends.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/statsd/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import homeassistant.util as util
|
||||||
|
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
|
||||||
|
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
|
||||||
|
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
|
||||||
|
STATE_BELOW_HORIZON)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "statsd"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
DEFAULT_HOST = 'localhost'
|
||||||
|
DEFAULT_PORT = 8125
|
||||||
|
DEFAULT_PREFIX = 'hass'
|
||||||
|
DEFAULT_RATE = 1
|
||||||
|
|
||||||
|
REQUIREMENTS = ['python-statsd==1.7.2']
|
||||||
|
|
||||||
|
CONF_HOST = 'host'
|
||||||
|
CONF_PORT = 'port'
|
||||||
|
CONF_PREFIX = 'prefix'
|
||||||
|
CONF_RATE = 'rate'
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Setup the StatsD component. """
|
||||||
|
|
||||||
|
from statsd.compat import NUM_TYPES
|
||||||
|
import statsd
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
host = conf[CONF_HOST]
|
||||||
|
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||||
|
sample_rate = util.convert(conf.get(CONF_RATE), int, DEFAULT_RATE)
|
||||||
|
prefix = util.convert(conf.get(CONF_PREFIX), str, DEFAULT_PREFIX)
|
||||||
|
|
||||||
|
statsd_connection = statsd.Connection(
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
sample_rate=sample_rate,
|
||||||
|
disabled=False
|
||||||
|
)
|
||||||
|
|
||||||
|
meter = statsd.Gauge(prefix, statsd_connection)
|
||||||
|
|
||||||
|
def statsd_event_listener(event):
|
||||||
|
""" Listen for new messages on the bus and sends them to StatsD. """
|
||||||
|
|
||||||
|
state = event.data.get('new_state')
|
||||||
|
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
||||||
|
_state = 1
|
||||||
|
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||||
|
STATE_BELOW_HORIZON):
|
||||||
|
_state = 0
|
||||||
|
else:
|
||||||
|
_state = state.state
|
||||||
|
if _state == '':
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
_state = float(_state)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not isinstance(_state, NUM_TYPES):
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug('Sending %s.%s', state.entity_id, _state)
|
||||||
|
meter.send(state.entity_id, _state)
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED, statsd_event_listener)
|
||||||
|
|
||||||
|
return True
|
@ -15,7 +15,7 @@ from homeassistant.helpers.event import (
|
|||||||
track_point_in_utc_time, track_utc_time_change)
|
track_point_in_utc_time, track_utc_time_change)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['astral==0.8.1']
|
REQUIREMENTS = ['astral==0.9']
|
||||||
DOMAIN = "sun"
|
DOMAIN = "sun"
|
||||||
ENTITY_ID = "sun.sun"
|
ENTITY_ID = "sun.sun"
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||||
|
ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
|
||||||
|
|
||||||
@ -29,7 +30,6 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|||||||
|
|
||||||
ATTR_TODAY_MWH = "today_mwh"
|
ATTR_TODAY_MWH = "today_mwh"
|
||||||
ATTR_CURRENT_POWER_MWH = "current_power_mwh"
|
ATTR_CURRENT_POWER_MWH = "current_power_mwh"
|
||||||
ATTR_SENSOR_STATE = "sensor_state"
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
@ -47,7 +47,6 @@ DISCOVERY_PLATFORMS = {
|
|||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
'current_power_mwh': ATTR_CURRENT_POWER_MWH,
|
'current_power_mwh': ATTR_CURRENT_POWER_MWH,
|
||||||
'today_power_mw': ATTR_TODAY_MWH,
|
'today_power_mw': ATTR_TODAY_MWH,
|
||||||
'sensor_state': ATTR_SENSOR_STATE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -71,6 +70,12 @@ def turn_off(hass, entity_id=None):
|
|||||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(hass, entity_id=None):
|
||||||
|
""" Toggle all or specified switch. """
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||||
|
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Track states and offer events for switches. """
|
""" Track states and offer events for switches. """
|
||||||
component = EntityComponent(
|
component = EntityComponent(
|
||||||
@ -85,6 +90,8 @@ def setup(hass, config):
|
|||||||
for switch in target_switches:
|
for switch in target_switches:
|
||||||
if service.service == SERVICE_TURN_ON:
|
if service.service == SERVICE_TURN_ON:
|
||||||
switch.turn_on()
|
switch.turn_on()
|
||||||
|
elif service.service == SERVICE_TOGGLE:
|
||||||
|
switch.toggle()
|
||||||
else:
|
else:
|
||||||
switch.turn_off()
|
switch.turn_off()
|
||||||
|
|
||||||
@ -97,6 +104,8 @@ def setup(hass, config):
|
|||||||
descriptions.get(SERVICE_TURN_OFF))
|
descriptions.get(SERVICE_TURN_OFF))
|
||||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service,
|
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service,
|
||||||
descriptions.get(SERVICE_TURN_ON))
|
descriptions.get(SERVICE_TURN_ON))
|
||||||
|
hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_switch_service,
|
||||||
|
descriptions.get(SERVICE_TOGGLE))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -120,11 +129,6 @@ class SwitchDevice(ToggleEntity):
|
|||||||
""" Is the device in standby. """
|
""" Is the device in standby. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def sensor_state(self):
|
|
||||||
""" Is the sensor on or off. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns device specific state attributes. """
|
""" Returns device specific state attributes. """
|
||||||
|
@ -27,12 +27,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# 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 Edimax Smart Plugs. """
|
""" Find and return Edimax Smart Plugs. """
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
from pyedimax.smartplug import SmartPlug
|
from pyedimax.smartplug import SmartPlug
|
||||||
except ImportError:
|
|
||||||
_LOGGER.error('Failed to import pyedimax')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# pylint: disable=global-statement
|
# pylint: disable=global-statement
|
||||||
# check for required values in configuration file
|
# check for required values in configuration file
|
||||||
|
@ -7,7 +7,6 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/sensor.mysensors.html
|
https://home-assistant.io/components/sensor.mysensors.html
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
@ -30,33 +29,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
for gateway in mysensors.GATEWAYS.values():
|
for gateway in mysensors.GATEWAYS.values():
|
||||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||||
# states.
|
# states. Map them in a defaultdict(list).
|
||||||
s_types = [
|
pres = gateway.const.Presentation
|
||||||
gateway.const.Presentation.S_DOOR,
|
set_req = gateway.const.SetReq
|
||||||
gateway.const.Presentation.S_MOTION,
|
map_sv_types = {
|
||||||
gateway.const.Presentation.S_SMOKE,
|
pres.S_DOOR: [set_req.V_ARMED],
|
||||||
gateway.const.Presentation.S_LIGHT,
|
pres.S_MOTION: [set_req.V_ARMED],
|
||||||
gateway.const.Presentation.S_LOCK,
|
pres.S_SMOKE: [set_req.V_ARMED],
|
||||||
]
|
pres.S_LIGHT: [set_req.V_LIGHT],
|
||||||
v_types = [
|
pres.S_LOCK: [set_req.V_LOCK_STATUS],
|
||||||
gateway.const.SetReq.V_ARMED,
|
}
|
||||||
gateway.const.SetReq.V_LIGHT,
|
|
||||||
gateway.const.SetReq.V_LOCK_STATUS,
|
|
||||||
]
|
|
||||||
if float(gateway.version) >= 1.5:
|
if float(gateway.version) >= 1.5:
|
||||||
s_types.extend([
|
map_sv_types.update({
|
||||||
gateway.const.Presentation.S_BINARY,
|
pres.S_BINARY: [set_req.V_STATUS, set_req.V_LIGHT],
|
||||||
gateway.const.Presentation.S_SPRINKLER,
|
pres.S_SPRINKLER: [set_req.V_STATUS],
|
||||||
gateway.const.Presentation.S_WATER_LEAK,
|
pres.S_WATER_LEAK: [set_req.V_ARMED],
|
||||||
gateway.const.Presentation.S_SOUND,
|
pres.S_SOUND: [set_req.V_ARMED],
|
||||||
gateway.const.Presentation.S_VIBRATION,
|
pres.S_VIBRATION: [set_req.V_ARMED],
|
||||||
gateway.const.Presentation.S_MOISTURE,
|
pres.S_MOISTURE: [set_req.V_ARMED],
|
||||||
])
|
})
|
||||||
v_types.extend([gateway.const.SetReq.V_STATUS, ])
|
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
|
||||||
|
|
||||||
devices = defaultdict(list)
|
devices = {}
|
||||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||||
s_types, v_types, devices, add_devices, MySensorsSwitch))
|
map_sv_types, devices, add_devices, MySensorsSwitch))
|
||||||
|
|
||||||
|
|
||||||
class MySensorsSwitch(SwitchDevice):
|
class MySensorsSwitch(SwitchDevice):
|
||||||
@ -104,8 +100,10 @@ class MySensorsSwitch(SwitchDevice):
|
|||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
device_attr = dict(self._values)
|
device_attr = {}
|
||||||
device_attr.pop(self.value_type, None)
|
for value_type, value in self._values.items():
|
||||||
|
if value_type != self.value_type:
|
||||||
|
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||||
return device_attr
|
return device_attr
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
DEFAULT_NAME = "Orvibo S20 Switch"
|
DEFAULT_NAME = "Orvibo S20 Switch"
|
||||||
REQUIREMENTS = ['orvibo==1.1.0']
|
REQUIREMENTS = ['orvibo==1.1.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
_WEMO_SUBSCRIPTION_REGISTRY = None
|
_WEMO_SUBSCRIPTION_REGISTRY = None
|
||||||
|
|
||||||
|
ATTR_SENSOR_STATE = "sensor_state"
|
||||||
|
ATTR_SWITCH_MODE = "switch_mode"
|
||||||
|
|
||||||
|
MAKER_SWITCH_MOMENTARY = "momentary"
|
||||||
|
MAKER_SWITCH_TOGGLE = "toggle"
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument, too-many-function-args
|
# pylint: disable=unused-argument, too-many-function-args
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
@ -88,6 +94,26 @@ class WemoSwitch(SwitchDevice):
|
|||||||
""" Returns the name of the switch if any. """
|
""" Returns the name of the switch if any. """
|
||||||
return self.wemo.name
|
return self.wemo.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
attr = super().state_attributes or {}
|
||||||
|
if self.maker_params:
|
||||||
|
# Is the maker sensor on or off.
|
||||||
|
if self.maker_params['hassensor']:
|
||||||
|
# Note a state of 1 matches the WeMo app 'not triggered'!
|
||||||
|
if self.maker_params['sensorstate']:
|
||||||
|
attr[ATTR_SENSOR_STATE] = STATE_OFF
|
||||||
|
else:
|
||||||
|
attr[ATTR_SENSOR_STATE] = STATE_ON
|
||||||
|
|
||||||
|
# Is the maker switch configured as toggle(0) or momentary (1).
|
||||||
|
if self.maker_params['switchmode']:
|
||||||
|
attr[ATTR_SWITCH_MODE] = MAKER_SWITCH_MOMENTARY
|
||||||
|
else:
|
||||||
|
attr[ATTR_SWITCH_MODE] = MAKER_SWITCH_TOGGLE
|
||||||
|
|
||||||
|
return attr
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state. """
|
""" Returns the state. """
|
||||||
@ -122,28 +148,6 @@ class WemoSwitch(SwitchDevice):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
|
||||||
def sensor_state(self):
|
|
||||||
""" Is the sensor on or off. """
|
|
||||||
if self.maker_params and self.has_sensor:
|
|
||||||
# Note a state of 1 matches the WeMo app 'not triggered'!
|
|
||||||
if self.maker_params['sensorstate']:
|
|
||||||
return STATE_OFF
|
|
||||||
else:
|
|
||||||
return STATE_ON
|
|
||||||
|
|
||||||
@property
|
|
||||||
def switch_mode(self):
|
|
||||||
""" Is the switch configured as toggle(0) or momentary (1). """
|
|
||||||
if self.maker_params:
|
|
||||||
return self.maker_params['switchmode']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def has_sensor(self):
|
|
||||||
""" Is the sensor present? """
|
|
||||||
if self.maker_params:
|
|
||||||
return self.maker_params['hassensor']
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
""" True if switch is on. """
|
""" True if switch is on. """
|
||||||
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.4.1']
|
REQUIREMENTS = ['python-wink==0.4.2']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -32,3 +32,4 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices(WinkToggleDevice(switch) for switch in pywink.get_switches())
|
add_devices(WinkToggleDevice(switch) for switch in pywink.get_switches())
|
||||||
add_devices(WinkToggleDevice(switch) for switch in
|
add_devices(WinkToggleDevice(switch) for switch in
|
||||||
pywink.get_powerstrip_outlets())
|
pywink.get_powerstrip_outlets())
|
||||||
|
add_devices(WinkToggleDevice(switch) for switch in pywink.get_sirens())
|
||||||
|
28
homeassistant/components/switch/zigbee.py
Normal file
28
homeassistant/components/switch/zigbee.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.zigbee
|
||||||
|
|
||||||
|
Contains functionality to use a ZigBee device as a switch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
from homeassistant.components.zigbee import (
|
||||||
|
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
|
||||||
|
|
||||||
|
|
||||||
|
DEPENDENCIES = ["zigbee"]
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""
|
||||||
|
Create and add an entity based on the configuration.
|
||||||
|
"""
|
||||||
|
add_entities([
|
||||||
|
ZigBeeSwitch(hass, ZigBeeDigitalOutConfig(config))
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeSwitch(ZigBeeDigitalOut, SwitchDevice):
|
||||||
|
"""
|
||||||
|
Use multiple inheritance to turn a ZigBeeDigitalOut into a SwitchDevice.
|
||||||
|
"""
|
||||||
|
pass
|
@ -6,9 +6,10 @@ Zwave platform that handles simple binary switches.
|
|||||||
"""
|
"""
|
||||||
# Because we do not compile openzwave on CI
|
# Because we do not compile openzwave on CI
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
import homeassistant.components.zwave as zwave
|
from homeassistant.components.switch import SwitchDevice, DOMAIN
|
||||||
|
from homeassistant.components.zwave import (
|
||||||
from homeassistant.components.switch import SwitchDevice
|
COMMAND_CLASS_SWITCH_BINARY, TYPE_BOOL, GENRE_USER, NETWORK,
|
||||||
|
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@ -17,28 +18,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
|
node = NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||||
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
|
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||||
|
|
||||||
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY:
|
if value.command_class != COMMAND_CLASS_SWITCH_BINARY:
|
||||||
return
|
return
|
||||||
if value.type != zwave.TYPE_BOOL:
|
if value.type != TYPE_BOOL:
|
||||||
return
|
return
|
||||||
if value.genre != zwave.GENRE_USER:
|
if value.genre != GENRE_USER:
|
||||||
return
|
return
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
add_devices([ZwaveSwitch(value)])
|
add_devices([ZwaveSwitch(value)])
|
||||||
|
|
||||||
|
|
||||||
class ZwaveSwitch(SwitchDevice):
|
class ZwaveSwitch(ZWaveDeviceEntity, SwitchDevice):
|
||||||
""" Provides a zwave switch. """
|
""" Provides a zwave switch. """
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
from openzwave.network import ZWaveNetwork
|
from openzwave.network import ZWaveNetwork
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
self._value = value
|
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||||
self._node = value.node
|
|
||||||
|
|
||||||
self._state = value.data
|
self._state = value.data
|
||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
@ -50,18 +50,6 @@ class ZwaveSwitch(SwitchDevice):
|
|||||||
self._state = value.data
|
self._state = value.data
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
""" No polling needed for a demo switch. """
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
""" Returns the name of the device if any. """
|
|
||||||
name = self._node.name or "{}".format(self._node.product_name)
|
|
||||||
|
|
||||||
return "{}".format(name or self._value.label)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
""" True if device is on. """
|
""" True if device is on. """
|
||||||
@ -69,8 +57,8 @@ class ZwaveSwitch(SwitchDevice):
|
|||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turn the device on. """
|
""" Turn the device on. """
|
||||||
self._node.set_switch(self._value.value_id, True)
|
self._value.node.set_switch(self._value.value_id, True)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" Turn the device off. """
|
""" Turn the device off. """
|
||||||
self._node.set_switch(self._value.value_id, False)
|
self._value.node.set_switch(self._value.value_id, False)
|
||||||
|
@ -27,6 +27,7 @@ SCAN_INTERVAL = 60
|
|||||||
|
|
||||||
SERVICE_SET_AWAY_MODE = "set_away_mode"
|
SERVICE_SET_AWAY_MODE = "set_away_mode"
|
||||||
SERVICE_SET_TEMPERATURE = "set_temperature"
|
SERVICE_SET_TEMPERATURE = "set_temperature"
|
||||||
|
SERVICE_SET_FAN_MODE = "set_fan_mode"
|
||||||
|
|
||||||
STATE_HEAT = "heat"
|
STATE_HEAT = "heat"
|
||||||
STATE_COOL = "cool"
|
STATE_COOL = "cool"
|
||||||
@ -34,6 +35,7 @@ STATE_IDLE = "idle"
|
|||||||
|
|
||||||
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
ATTR_AWAY_MODE = "away_mode"
|
ATTR_AWAY_MODE = "away_mode"
|
||||||
|
ATTR_FAN = "fan"
|
||||||
ATTR_MAX_TEMP = "max_temp"
|
ATTR_MAX_TEMP = "max_temp"
|
||||||
ATTR_MIN_TEMP = "min_temp"
|
ATTR_MIN_TEMP = "min_temp"
|
||||||
ATTR_TEMPERATURE_LOW = "target_temp_low"
|
ATTR_TEMPERATURE_LOW = "target_temp_low"
|
||||||
@ -69,34 +71,58 @@ def set_temperature(hass, temperature, entity_id=None):
|
|||||||
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, data)
|
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, data)
|
||||||
|
|
||||||
|
|
||||||
|
def set_fan_mode(hass, fan_mode, entity_id=None):
|
||||||
|
""" Turn all or specified thermostat fan mode on. """
|
||||||
|
data = {
|
||||||
|
ATTR_FAN: fan_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup thermostats. """
|
""" Setup thermostats. """
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
||||||
SCAN_INTERVAL, DISCOVERY_PLATFORMS)
|
SCAN_INTERVAL, DISCOVERY_PLATFORMS)
|
||||||
component.setup(config)
|
component.setup(config)
|
||||||
|
|
||||||
def thermostat_service(service):
|
descriptions = load_yaml_config_file(
|
||||||
""" Handles calls to the services. """
|
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||||
|
|
||||||
|
def away_mode_set_service(service):
|
||||||
|
""" Set away mode on target thermostats """
|
||||||
|
|
||||||
# Convert the entity ids to valid light ids
|
|
||||||
target_thermostats = component.extract_from_service(service)
|
target_thermostats = component.extract_from_service(service)
|
||||||
|
|
||||||
if service.service == SERVICE_SET_AWAY_MODE:
|
|
||||||
away_mode = service.data.get(ATTR_AWAY_MODE)
|
away_mode = service.data.get(ATTR_AWAY_MODE)
|
||||||
|
|
||||||
if away_mode is None:
|
if away_mode is None:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Received call to %s without attribute %s",
|
"Received call to %s without attribute %s",
|
||||||
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
|
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
|
||||||
|
return
|
||||||
|
|
||||||
elif away_mode:
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
|
if away_mode:
|
||||||
thermostat.turn_away_mode_on()
|
thermostat.turn_away_mode_on()
|
||||||
else:
|
else:
|
||||||
for thermostat in target_thermostats:
|
|
||||||
thermostat.turn_away_mode_off()
|
thermostat.turn_away_mode_off()
|
||||||
|
|
||||||
elif service.service == SERVICE_SET_TEMPERATURE:
|
thermostat.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_SET_AWAY_MODE, away_mode_set_service,
|
||||||
|
descriptions.get(SERVICE_SET_AWAY_MODE))
|
||||||
|
|
||||||
|
def temperature_set_service(service):
|
||||||
|
""" Set temperature on the target thermostats """
|
||||||
|
|
||||||
|
target_thermostats = component.extract_from_service(service)
|
||||||
|
|
||||||
temperature = util.convert(
|
temperature = util.convert(
|
||||||
service.data.get(ATTR_TEMPERATURE), float)
|
service.data.get(ATTR_TEMPERATURE), float)
|
||||||
|
|
||||||
@ -108,20 +134,37 @@ def setup(hass, config):
|
|||||||
temperature, hass.config.temperature_unit,
|
temperature, hass.config.temperature_unit,
|
||||||
thermostat.unit_of_measurement))
|
thermostat.unit_of_measurement))
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
|
||||||
thermostat.update_ha_state(True)
|
thermostat.update_ha_state(True)
|
||||||
|
|
||||||
descriptions = load_yaml_config_file(
|
|
||||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.register(
|
||||||
DOMAIN, SERVICE_SET_AWAY_MODE, thermostat_service,
|
DOMAIN, SERVICE_SET_TEMPERATURE, temperature_set_service,
|
||||||
descriptions.get(SERVICE_SET_AWAY_MODE))
|
|
||||||
|
|
||||||
hass.services.register(
|
|
||||||
DOMAIN, SERVICE_SET_TEMPERATURE, thermostat_service,
|
|
||||||
descriptions.get(SERVICE_SET_TEMPERATURE))
|
descriptions.get(SERVICE_SET_TEMPERATURE))
|
||||||
|
|
||||||
|
def fan_mode_set_service(service):
|
||||||
|
""" Set fan mode on target thermostats """
|
||||||
|
|
||||||
|
target_thermostats = component.extract_from_service(service)
|
||||||
|
|
||||||
|
fan_mode = service.data.get(ATTR_FAN)
|
||||||
|
|
||||||
|
if fan_mode is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Received call to %s without attribute %s",
|
||||||
|
SERVICE_SET_FAN_MODE, ATTR_FAN)
|
||||||
|
return
|
||||||
|
|
||||||
|
for thermostat in target_thermostats:
|
||||||
|
if fan_mode:
|
||||||
|
thermostat.turn_fan_on()
|
||||||
|
else:
|
||||||
|
thermostat.turn_fan_off()
|
||||||
|
|
||||||
|
thermostat.update_ha_state(True)
|
||||||
|
|
||||||
|
hass.services.register(
|
||||||
|
DOMAIN, SERVICE_SET_FAN_MODE, fan_mode_set_service,
|
||||||
|
descriptions.get(SERVICE_SET_FAN_MODE))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -164,6 +207,10 @@ class ThermostatDevice(Entity):
|
|||||||
if is_away is not None:
|
if is_away is not None:
|
||||||
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
|
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
|
||||||
|
|
||||||
|
is_fan_on = self.is_fan_on
|
||||||
|
if is_fan_on is not None:
|
||||||
|
data[ATTR_FAN] = STATE_ON if is_fan_on else STATE_OFF
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
device_attr = self.device_state_attributes
|
||||||
|
|
||||||
if device_attr is not None:
|
if device_attr is not None:
|
||||||
@ -209,6 +256,14 @@ class ThermostatDevice(Entity):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fan_on(self):
|
||||||
|
"""
|
||||||
|
Returns if the fan is on
|
||||||
|
Return None if not available.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
def set_temperate(self, temperature):
|
def set_temperate(self, temperature):
|
||||||
""" Set new target temperature. """
|
""" Set new target temperature. """
|
||||||
pass
|
pass
|
||||||
@ -221,6 +276,14 @@ class ThermostatDevice(Entity):
|
|||||||
""" Turns away mode off. """
|
""" Turns away mode off. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def turn_fan_on(self):
|
||||||
|
""" Turns fan on. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
def turn_fan_off(self):
|
||||||
|
""" Turns fan off. """
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
""" Return minimum temperature. """
|
""" Return minimum temperature. """
|
||||||
|
@ -11,38 +11,21 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL,
|
from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL,
|
||||||
STATE_IDLE, STATE_HEAT)
|
STATE_IDLE, STATE_HEAT)
|
||||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
|
from homeassistant.const import (TEMP_CELCIUS)
|
||||||
|
import homeassistant.components.nest as nest
|
||||||
|
|
||||||
REQUIREMENTS = ['python-nest==2.6.0']
|
DEPENDENCIES = ['nest']
|
||||||
|
|
||||||
|
|
||||||
# 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 nest thermostat. """
|
"Setup nest thermostat"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
username = config.get(CONF_USERNAME)
|
|
||||||
password = config.get(CONF_PASSWORD)
|
|
||||||
|
|
||||||
if username is None or password is None:
|
|
||||||
logger.error("Missing required configuration items %s or %s",
|
|
||||||
CONF_USERNAME, CONF_PASSWORD)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
import nest
|
|
||||||
except ImportError:
|
|
||||||
logger.exception(
|
|
||||||
"Error while importing dependency nest. "
|
|
||||||
"Did you maybe not install the python-nest dependency?")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
napi = nest.Nest(username, password)
|
|
||||||
try:
|
try:
|
||||||
add_devices([
|
add_devices([
|
||||||
NestThermostat(structure, device)
|
NestThermostat(structure, device)
|
||||||
for structure in napi.structures
|
for structure in nest.NEST.structures
|
||||||
for device in structure.devices
|
for device in structure.devices
|
||||||
])
|
])
|
||||||
except socket.error:
|
except socket.error:
|
||||||
@ -83,7 +66,6 @@ class NestThermostat(ThermostatDevice):
|
|||||||
return {
|
return {
|
||||||
"humidity": self.device.humidity,
|
"humidity": self.device.humidity,
|
||||||
"target_humidity": self.device.target_humidity,
|
"target_humidity": self.device.target_humidity,
|
||||||
"fan": self.device.fan,
|
|
||||||
"mode": self.device.mode
|
"mode": self.device.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +142,19 @@ class NestThermostat(ThermostatDevice):
|
|||||||
""" Turns away off. """
|
""" Turns away off. """
|
||||||
self.structure.away = False
|
self.structure.away = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fan_on(self):
|
||||||
|
""" Returns whether the fan is on """
|
||||||
|
return self.device.fan
|
||||||
|
|
||||||
|
def turn_fan_on(self):
|
||||||
|
""" Turns fan on """
|
||||||
|
self.device.fan = True
|
||||||
|
|
||||||
|
def turn_fan_off(self):
|
||||||
|
""" Turns fan off """
|
||||||
|
self.device.fan = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
""" Identifies min_temp in Nest API or defaults if not available. """
|
""" Identifies min_temp in Nest API or defaults if not available. """
|
||||||
|
@ -21,13 +21,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Radio Thermostat. """
|
""" Sets up the Radio Thermostat. """
|
||||||
try:
|
|
||||||
import radiotherm
|
import radiotherm
|
||||||
except ImportError:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unable to import radiotherm. "
|
|
||||||
"Did you maybe not install the 'radiotherm' package?")
|
|
||||||
return False
|
|
||||||
|
|
||||||
hosts = []
|
hosts = []
|
||||||
if CONF_HOST in config:
|
if CONF_HOST in config:
|
||||||
|
@ -22,3 +22,15 @@ set_temperature:
|
|||||||
temperature:
|
temperature:
|
||||||
description: New target temperature for thermostat
|
description: New target temperature for thermostat
|
||||||
example: 25
|
example: 25
|
||||||
|
|
||||||
|
set_fan_mode:
|
||||||
|
description: Turn fan on/off for a thermostat
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities to change
|
||||||
|
example: 'thermostat.nest'
|
||||||
|
|
||||||
|
fan:
|
||||||
|
description: New value of fan mode
|
||||||
|
example: true
|
||||||
|
@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
|||||||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||||
|
|
||||||
DEPENDENCIES = ['alarm_control_panel']
|
DEPENDENCIES = ['alarm_control_panel']
|
||||||
REQUIREMENTS = ['vsure==0.4.5']
|
REQUIREMENTS = ['vsure==0.4.8']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.const import (
|
|||||||
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
|
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
|
||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
REQUIREMENTS = ['python-wink==0.4.1']
|
REQUIREMENTS = ['python-wink==0.4.2']
|
||||||
|
|
||||||
DISCOVER_LIGHTS = "wink.lights"
|
DISCOVER_LIGHTS = "wink.lights"
|
||||||
DISCOVER_SWITCHES = "wink.switches"
|
DISCOVER_SWITCHES = "wink.switches"
|
||||||
@ -38,6 +38,7 @@ def setup(hass, config):
|
|||||||
for component_name, func_exists, discovery_type in (
|
for component_name, func_exists, discovery_type in (
|
||||||
('light', pywink.get_bulbs, DISCOVER_LIGHTS),
|
('light', pywink.get_bulbs, DISCOVER_LIGHTS),
|
||||||
('switch', lambda: pywink.get_switches or
|
('switch', lambda: pywink.get_switches or
|
||||||
|
pywink.get_sirens or
|
||||||
pywink.get_powerstrip_outlets, DISCOVER_SWITCHES),
|
pywink.get_powerstrip_outlets, DISCOVER_SWITCHES),
|
||||||
('sensor', lambda: pywink.get_sensors or
|
('sensor', lambda: pywink.get_sensors or
|
||||||
pywink.get_eggtrays, DISCOVER_SENSORS),
|
pywink.get_eggtrays, DISCOVER_SENSORS),
|
||||||
|
327
homeassistant/components/zigbee.py
Normal file
327
homeassistant/components/zigbee.py
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.zigbee
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Sets up and provides access to a ZigBee device and contains generic entity
|
||||||
|
classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
|
||||||
|
from homeassistant.core import JobPriority
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = "zigbee"
|
||||||
|
REQUIREMENTS = ("xbee-helper==0.0.6",)
|
||||||
|
|
||||||
|
CONF_DEVICE = "device"
|
||||||
|
CONF_BAUD = "baud"
|
||||||
|
|
||||||
|
DEFAULT_DEVICE = "/dev/ttyUSB0"
|
||||||
|
DEFAULT_BAUD = 9600
|
||||||
|
DEFAULT_ADC_MAX_VOLTS = 1.2
|
||||||
|
|
||||||
|
# Copied from xbee_helper during setup()
|
||||||
|
GPIO_DIGITAL_OUTPUT_LOW = None
|
||||||
|
GPIO_DIGITAL_OUTPUT_HIGH = None
|
||||||
|
ADC_PERCENTAGE = None
|
||||||
|
ZIGBEE_EXCEPTION = None
|
||||||
|
ZIGBEE_TX_FAILURE = None
|
||||||
|
|
||||||
|
DEVICE = None
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""
|
||||||
|
Set up the connection to the ZigBee device and instantiate the helper
|
||||||
|
class for it.
|
||||||
|
"""
|
||||||
|
global DEVICE
|
||||||
|
global GPIO_DIGITAL_OUTPUT_LOW
|
||||||
|
global GPIO_DIGITAL_OUTPUT_HIGH
|
||||||
|
global ADC_PERCENTAGE
|
||||||
|
global ZIGBEE_EXCEPTION
|
||||||
|
global ZIGBEE_TX_FAILURE
|
||||||
|
|
||||||
|
import xbee_helper.const as xb_const
|
||||||
|
from xbee_helper import ZigBee
|
||||||
|
from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure
|
||||||
|
from serial import Serial, SerialException
|
||||||
|
|
||||||
|
GPIO_DIGITAL_OUTPUT_LOW = xb_const.GPIO_DIGITAL_OUTPUT_LOW
|
||||||
|
GPIO_DIGITAL_OUTPUT_HIGH = xb_const.GPIO_DIGITAL_OUTPUT_HIGH
|
||||||
|
ADC_PERCENTAGE = xb_const.ADC_PERCENTAGE
|
||||||
|
ZIGBEE_EXCEPTION = ZigBeeException
|
||||||
|
ZIGBEE_TX_FAILURE = ZigBeeTxFailure
|
||||||
|
|
||||||
|
usb_device = config[DOMAIN].get(CONF_DEVICE, DEFAULT_DEVICE)
|
||||||
|
baud = int(config[DOMAIN].get(CONF_BAUD, DEFAULT_BAUD))
|
||||||
|
try:
|
||||||
|
ser = Serial(usb_device, baud)
|
||||||
|
except SerialException as exc:
|
||||||
|
_LOGGER.exception("Unable to open serial port for ZigBee: %s", exc)
|
||||||
|
return False
|
||||||
|
DEVICE = ZigBee(ser)
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_serial_port)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def close_serial_port(*args):
|
||||||
|
"""
|
||||||
|
Close the serial port we're using to communicate with the ZigBee.
|
||||||
|
"""
|
||||||
|
DEVICE.zb.serial.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeConfig(object):
|
||||||
|
"""
|
||||||
|
Handles the fetching of configuration from the config file for any ZigBee
|
||||||
|
entity.
|
||||||
|
"""
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config
|
||||||
|
self._should_poll = config.get("poll", True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
The name given to the entity.
|
||||||
|
"""
|
||||||
|
return self._config["name"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
"""
|
||||||
|
If an address has been provided, unhexlify it, otherwise return None
|
||||||
|
as we're talking to our local ZigBee device.
|
||||||
|
"""
|
||||||
|
address = self._config.get("address")
|
||||||
|
if address is not None:
|
||||||
|
address = unhexlify(address)
|
||||||
|
return address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""
|
||||||
|
A bool depicting whether HA should repeatedly poll this device for its
|
||||||
|
value.
|
||||||
|
"""
|
||||||
|
return self._should_poll
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeePinConfig(ZigBeeConfig):
|
||||||
|
"""
|
||||||
|
Handles the fetching of configuration from the config file for a ZigBee
|
||||||
|
GPIO pin.
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def pin(self):
|
||||||
|
"""
|
||||||
|
The GPIO pin number.
|
||||||
|
"""
|
||||||
|
return self._config["pin"]
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeDigitalPinConfig(ZigBeePinConfig):
|
||||||
|
"""
|
||||||
|
Handles the fetching of configuration from the config file for a ZigBee
|
||||||
|
GPIO pin set to digital in or out.
|
||||||
|
"""
|
||||||
|
def __init__(self, config):
|
||||||
|
super(ZigBeeDigitalPinConfig, self).__init__(config)
|
||||||
|
self._bool2state, self._state2bool = self.boolean_maps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boolean_maps(self):
|
||||||
|
"""
|
||||||
|
Create dicts to map booleans to pin high/low and vice versa. Depends on
|
||||||
|
the config item "on_state" which should be set to "low" or "high".
|
||||||
|
"""
|
||||||
|
if self._config.get("on_state", "").lower() == "low":
|
||||||
|
bool2state = {
|
||||||
|
True: GPIO_DIGITAL_OUTPUT_LOW,
|
||||||
|
False: GPIO_DIGITAL_OUTPUT_HIGH
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
bool2state = {
|
||||||
|
True: GPIO_DIGITAL_OUTPUT_HIGH,
|
||||||
|
False: GPIO_DIGITAL_OUTPUT_LOW
|
||||||
|
}
|
||||||
|
state2bool = {v: k for k, v in bool2state.items()}
|
||||||
|
return bool2state, state2bool
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bool2state(self):
|
||||||
|
"""
|
||||||
|
A dictionary mapping booleans to GPIOSetting objects to translate
|
||||||
|
on/off as being pin high or low.
|
||||||
|
"""
|
||||||
|
return self._bool2state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state2bool(self):
|
||||||
|
"""
|
||||||
|
A dictionary mapping GPIOSetting objects to booleans to translate
|
||||||
|
pin high/low as being on or off.
|
||||||
|
"""
|
||||||
|
return self._state2bool
|
||||||
|
|
||||||
|
# Create an alias so that ZigBeeDigitalOutConfig has a logical opposite.
|
||||||
|
ZigBeeDigitalInConfig = ZigBeeDigitalPinConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeDigitalOutConfig(ZigBeeDigitalPinConfig):
|
||||||
|
"""
|
||||||
|
A subclass of ZigBeeDigitalPinConfig which sets _should_poll to default as
|
||||||
|
False instead of True. The value will still be overridden by the presence
|
||||||
|
of a 'poll' config entry.
|
||||||
|
"""
|
||||||
|
def __init__(self, config):
|
||||||
|
super(ZigBeeDigitalOutConfig, self).__init__(config)
|
||||||
|
self._should_poll = config.get("poll", False)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeAnalogInConfig(ZigBeePinConfig):
|
||||||
|
"""
|
||||||
|
Handles the fetching of configuration from the config file for a ZigBee
|
||||||
|
GPIO pin set to analog in.
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def max_voltage(self):
|
||||||
|
"""
|
||||||
|
The voltage at which the ADC will report its highest value.
|
||||||
|
"""
|
||||||
|
return float(self._config.get("max_volts", DEFAULT_ADC_MAX_VOLTS))
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeDigitalIn(Entity):
|
||||||
|
"""
|
||||||
|
Represents a GPIO pin configured as a digital input.
|
||||||
|
"""
|
||||||
|
def __init__(self, hass, config):
|
||||||
|
self._config = config
|
||||||
|
self._state = False
|
||||||
|
# Get initial state
|
||||||
|
hass.pool.add_job(
|
||||||
|
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._config.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
return self._config.should_poll
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""
|
||||||
|
Returns True if the Entity is on, else False.
|
||||||
|
"""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Ask the ZigBee device what its output is set to.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pin_state = DEVICE.get_gpio_pin(
|
||||||
|
self._config.pin,
|
||||||
|
self._config.address)
|
||||||
|
except ZIGBEE_TX_FAILURE:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Transmission failure when attempting to get sample from "
|
||||||
|
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||||
|
return
|
||||||
|
except ZIGBEE_EXCEPTION as exc:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to get sample from ZigBee device: %s", exc)
|
||||||
|
return
|
||||||
|
self._state = self._config.state2bool[pin_state]
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeDigitalOut(ZigBeeDigitalIn):
|
||||||
|
"""
|
||||||
|
Adds functionality to ZigBeeDigitalIn to control an output.
|
||||||
|
"""
|
||||||
|
def _set_state(self, state):
|
||||||
|
try:
|
||||||
|
DEVICE.set_gpio_pin(
|
||||||
|
self._config.pin,
|
||||||
|
self._config.bool2state[state],
|
||||||
|
self._config.address)
|
||||||
|
except ZIGBEE_TX_FAILURE:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Transmission failure when attempting to set output pin on "
|
||||||
|
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||||
|
return
|
||||||
|
except ZIGBEE_EXCEPTION as exc:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to set digital pin on ZigBee device: %s", exc)
|
||||||
|
return
|
||||||
|
self._state = state
|
||||||
|
if not self.should_poll:
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Set the digital output to its 'on' state.
|
||||||
|
"""
|
||||||
|
self._set_state(True)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Set the digital output to its 'off' state.
|
||||||
|
"""
|
||||||
|
self._set_state(False)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigBeeAnalogIn(Entity):
|
||||||
|
"""
|
||||||
|
Represents a GPIO pin configured as an analog input.
|
||||||
|
"""
|
||||||
|
def __init__(self, hass, config):
|
||||||
|
self._config = config
|
||||||
|
self._value = None
|
||||||
|
# Get initial state
|
||||||
|
hass.pool.add_job(
|
||||||
|
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._config.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
return self._config.should_poll
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
return "%"
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Get the latest reading from the ADC.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._value = DEVICE.read_analog_pin(
|
||||||
|
self._config.pin,
|
||||||
|
self._config.max_voltage,
|
||||||
|
self._config.address,
|
||||||
|
ADC_PERCENTAGE)
|
||||||
|
except ZIGBEE_TX_FAILURE:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Transmission failure when attempting to get sample from "
|
||||||
|
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||||
|
except ZIGBEE_EXCEPTION as exc:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to get sample from ZigBee device: %s", exc)
|
@ -10,8 +10,8 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_HIDDEN, ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME)
|
ATTR_HIDDEN, ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME)
|
||||||
from homeassistant.helpers import extract_domain_configs, generate_entity_id
|
from homeassistant.helpers import extract_domain_configs
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity, generate_entity_id
|
||||||
from homeassistant.util.location import distance
|
from homeassistant.util.location import distance
|
||||||
|
|
||||||
DOMAIN = "zone"
|
DOMAIN = "zone"
|
||||||
@ -24,6 +24,9 @@ DEFAULT_NAME = 'Unnamed zone'
|
|||||||
ATTR_RADIUS = 'radius'
|
ATTR_RADIUS = 'radius'
|
||||||
DEFAULT_RADIUS = 100
|
DEFAULT_RADIUS = 100
|
||||||
|
|
||||||
|
ATTR_PASSIVE = 'passive'
|
||||||
|
DEFAULT_PASSIVE = False
|
||||||
|
|
||||||
ICON_HOME = 'mdi:home'
|
ICON_HOME = 'mdi:home'
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +40,9 @@ def active_zone(hass, latitude, longitude, radius=0):
|
|||||||
closest = None
|
closest = None
|
||||||
|
|
||||||
for zone in zones:
|
for zone in zones:
|
||||||
|
if zone.attributes.get(ATTR_PASSIVE):
|
||||||
|
continue
|
||||||
|
|
||||||
zone_dist = distance(
|
zone_dist = distance(
|
||||||
latitude, longitude,
|
latitude, longitude,
|
||||||
zone.attributes[ATTR_LATITUDE], zone.attributes[ATTR_LONGITUDE])
|
zone.attributes[ATTR_LATITUDE], zone.attributes[ATTR_LONGITUDE])
|
||||||
@ -78,13 +84,14 @@ def setup(hass, config):
|
|||||||
longitude = entry.get(ATTR_LONGITUDE)
|
longitude = entry.get(ATTR_LONGITUDE)
|
||||||
radius = entry.get(ATTR_RADIUS, DEFAULT_RADIUS)
|
radius = entry.get(ATTR_RADIUS, DEFAULT_RADIUS)
|
||||||
icon = entry.get(ATTR_ICON)
|
icon = entry.get(ATTR_ICON)
|
||||||
|
passive = entry.get(ATTR_PASSIVE, DEFAULT_PASSIVE)
|
||||||
|
|
||||||
if None in (latitude, longitude):
|
if None in (latitude, longitude):
|
||||||
logging.getLogger(__name__).error(
|
logging.getLogger(__name__).error(
|
||||||
'Each zone needs a latitude and longitude.')
|
'Each zone needs a latitude and longitude.')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
zone = Zone(hass, name, latitude, longitude, radius, icon)
|
zone = Zone(hass, name, latitude, longitude, radius, icon, passive)
|
||||||
zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name,
|
zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name,
|
||||||
entities)
|
entities)
|
||||||
zone.update_ha_state()
|
zone.update_ha_state()
|
||||||
@ -92,7 +99,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
if ENTITY_ID_HOME not in entities:
|
if ENTITY_ID_HOME not in entities:
|
||||||
zone = Zone(hass, hass.config.location_name, hass.config.latitude,
|
zone = Zone(hass, hass.config.location_name, hass.config.latitude,
|
||||||
hass.config.longitude, DEFAULT_RADIUS, ICON_HOME)
|
hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False)
|
||||||
zone.entity_id = ENTITY_ID_HOME
|
zone.entity_id = ENTITY_ID_HOME
|
||||||
zone.update_ha_state()
|
zone.update_ha_state()
|
||||||
|
|
||||||
@ -101,17 +108,15 @@ def setup(hass, config):
|
|||||||
|
|
||||||
class Zone(Entity):
|
class Zone(Entity):
|
||||||
""" Represents a Zone in Home Assistant. """
|
""" Represents a Zone in Home Assistant. """
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
def __init__(self, hass, name, latitude, longitude, radius, icon):
|
def __init__(self, hass, name, latitude, longitude, radius, icon, passive):
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
self.latitude = latitude
|
self._latitude = latitude
|
||||||
self.longitude = longitude
|
self._longitude = longitude
|
||||||
self.radius = radius
|
self._radius = radius
|
||||||
self._icon = icon
|
self._icon = icon
|
||||||
|
self._passive = passive
|
||||||
def should_poll(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -128,9 +133,12 @@ class Zone(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
return {
|
data = {
|
||||||
ATTR_HIDDEN: True,
|
ATTR_HIDDEN: True,
|
||||||
ATTR_LATITUDE: self.latitude,
|
ATTR_LATITUDE: self._latitude,
|
||||||
ATTR_LONGITUDE: self.longitude,
|
ATTR_LONGITUDE: self._longitude,
|
||||||
ATTR_RADIUS: self.radius,
|
ATTR_RADIUS: self._radius,
|
||||||
}
|
}
|
||||||
|
if self._passive:
|
||||||
|
data[ATTR_PASSIVE] = self._passive
|
||||||
|
return data
|
||||||
|
@ -10,11 +10,13 @@ import sys
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
from homeassistant.util import slugify
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||||
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED)
|
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED,
|
||||||
|
ATTR_BATTERY_LEVEL, ATTR_LOCATION)
|
||||||
|
|
||||||
|
|
||||||
DOMAIN = "zwave"
|
DOMAIN = "zwave"
|
||||||
REQUIREMENTS = ['pydispatcher==2.0.5']
|
REQUIREMENTS = ['pydispatcher==2.0.5']
|
||||||
@ -211,3 +213,62 @@ def setup(hass, config):
|
|||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ZWaveDeviceEntity:
|
||||||
|
""" Represents a ZWave node entity within Home Assistant. """
|
||||||
|
def __init__(self, value, domain):
|
||||||
|
self._value = value
|
||||||
|
self.entity_id = "{}.{}".format(domain, self._object_id())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" False because we will push our own state to HA when changed. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
""" Returns a unique id. """
|
||||||
|
return "ZWAVE-{}-{}".format(self._value.node.node_id,
|
||||||
|
self._value.object_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
name = self._value.node.name or "{} {}".format(
|
||||||
|
self._value.node.manufacturer_name, self._value.node.product_name)
|
||||||
|
|
||||||
|
return "{} {}".format(name, self._value.label)
|
||||||
|
|
||||||
|
def _object_id(self):
|
||||||
|
""" Returns the object_id of the device value.
|
||||||
|
The object_id contains node_id and value instance id
|
||||||
|
to not collide with other entity_ids"""
|
||||||
|
|
||||||
|
object_id = "{}_{}".format(slugify(self.name),
|
||||||
|
self._value.node.node_id)
|
||||||
|
|
||||||
|
# Add the instance id if there is more than one instance for the value
|
||||||
|
if self._value.instance > 1:
|
||||||
|
return "{}_{}".format(object_id, self._value.instance)
|
||||||
|
|
||||||
|
return object_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
""" Returns the state attributes. """
|
||||||
|
attrs = {
|
||||||
|
ATTR_NODE_ID: self._value.node.node_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
battery_level = self._value.node.get_battery_level()
|
||||||
|
|
||||||
|
if battery_level is not None:
|
||||||
|
attrs[ATTR_BATTERY_LEVEL] = battery_level
|
||||||
|
|
||||||
|
location = self._value.node.location
|
||||||
|
|
||||||
|
if location:
|
||||||
|
attrs[ATTR_LOCATION] = location
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
@ -12,6 +12,7 @@ from homeassistant.const import (
|
|||||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||||
CONF_TIME_ZONE)
|
CONF_TIME_ZONE)
|
||||||
import homeassistant.util.location as loc_util
|
import homeassistant.util.location as loc_util
|
||||||
|
from homeassistant.util.yaml import load_yaml
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -113,40 +114,9 @@ def find_config_file(config_dir):
|
|||||||
return config_path if os.path.isfile(config_path) else None
|
return config_path if os.path.isfile(config_path) else None
|
||||||
|
|
||||||
|
|
||||||
def load_config_file(config_path):
|
|
||||||
""" Loads given config file. """
|
|
||||||
return load_yaml_config_file(config_path)
|
|
||||||
|
|
||||||
|
|
||||||
def load_yaml_config_file(config_path):
|
def load_yaml_config_file(config_path):
|
||||||
""" Parse a YAML configuration file. """
|
""" Parse a YAML configuration file. """
|
||||||
import yaml
|
conf_dict = load_yaml(config_path)
|
||||||
|
|
||||||
def parse(fname):
|
|
||||||
""" Parse a YAML file. """
|
|
||||||
try:
|
|
||||||
with open(fname, encoding='utf-8') as conf_file:
|
|
||||||
# If configuration file is empty YAML returns None
|
|
||||||
# We convert that to an empty dict
|
|
||||||
return yaml.load(conf_file) or {}
|
|
||||||
except yaml.YAMLError:
|
|
||||||
error = 'Error reading YAML configuration file {}'.format(fname)
|
|
||||||
_LOGGER.exception(error)
|
|
||||||
raise HomeAssistantError(error)
|
|
||||||
|
|
||||||
def yaml_include(loader, node):
|
|
||||||
"""
|
|
||||||
Loads another YAML file and embeds it using the !include tag.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
device_tracker: !include device_tracker.yaml
|
|
||||||
"""
|
|
||||||
fname = os.path.join(os.path.dirname(loader.name), node.value)
|
|
||||||
return parse(fname)
|
|
||||||
|
|
||||||
yaml.add_constructor('!include', yaml_include)
|
|
||||||
|
|
||||||
conf_dict = parse(config_path)
|
|
||||||
|
|
||||||
if not isinstance(conf_dict, dict):
|
if not isinstance(conf_dict, dict):
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
""" Constants used by Home Assistant components. """
|
""" Constants used by Home Assistant components. """
|
||||||
|
|
||||||
__version__ = "0.11.1"
|
__version__ = "0.12.0"
|
||||||
|
|
||||||
# Can be used to specify a catch all when registering state or event listeners.
|
# Can be used to specify a catch all when registering state or event listeners.
|
||||||
MATCH_ALL = '*'
|
MATCH_ALL = '*'
|
||||||
@ -10,6 +10,7 @@ MATCH_ALL = '*'
|
|||||||
DEVICE_DEFAULT_NAME = "Unnamed Device"
|
DEVICE_DEFAULT_NAME = "Unnamed Device"
|
||||||
|
|
||||||
# #### CONFIG ####
|
# #### CONFIG ####
|
||||||
|
CONF_ICON = "icon"
|
||||||
CONF_LATITUDE = "latitude"
|
CONF_LATITUDE = "latitude"
|
||||||
CONF_LONGITUDE = "longitude"
|
CONF_LONGITUDE = "longitude"
|
||||||
CONF_TEMPERATURE_UNIT = "temperature_unit"
|
CONF_TEMPERATURE_UNIT = "temperature_unit"
|
||||||
@ -126,6 +127,7 @@ SERVICE_HOMEASSISTANT_STOP = "stop"
|
|||||||
|
|
||||||
SERVICE_TURN_ON = 'turn_on'
|
SERVICE_TURN_ON = 'turn_on'
|
||||||
SERVICE_TURN_OFF = 'turn_off'
|
SERVICE_TURN_OFF = 'turn_off'
|
||||||
|
SERVICE_TOGGLE = 'toggle'
|
||||||
|
|
||||||
SERVICE_VOLUME_UP = "volume_up"
|
SERVICE_VOLUME_UP = "volume_up"
|
||||||
SERVICE_VOLUME_DOWN = "volume_down"
|
SERVICE_VOLUME_DOWN = "volume_down"
|
||||||
|
@ -11,7 +11,6 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
import enum
|
import enum
|
||||||
import re
|
|
||||||
import functools as ft
|
import functools as ft
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
@ -26,6 +25,7 @@ from homeassistant.exceptions import (
|
|||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.util.location as location
|
import homeassistant.util.location as location
|
||||||
|
from homeassistant.helpers.entity import valid_entity_id, split_entity_id
|
||||||
import homeassistant.helpers.temperature as temp_helper
|
import homeassistant.helpers.temperature as temp_helper
|
||||||
from homeassistant.config import get_default_config_dir
|
from homeassistant.config import get_default_config_dir
|
||||||
|
|
||||||
@ -42,9 +42,6 @@ SERVICE_CALL_LIMIT = 10 # seconds
|
|||||||
# will be added for each component that polls devices.
|
# will be added for each component that polls devices.
|
||||||
MIN_WORKER_THREAD = 2
|
MIN_WORKER_THREAD = 2
|
||||||
|
|
||||||
# Pattern for validating entity IDs (format: <domain>.<entity>)
|
|
||||||
ENTITY_ID_PATTERN = re.compile(r"^(?P<domain>\w+)\.(?P<entity>\w+)$")
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Temporary to support deprecated methods
|
# Temporary to support deprecated methods
|
||||||
@ -339,7 +336,7 @@ class State(object):
|
|||||||
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
def __init__(self, entity_id, state, attributes=None, last_changed=None,
|
||||||
last_updated=None):
|
last_updated=None):
|
||||||
"""Initialize a new state."""
|
"""Initialize a new state."""
|
||||||
if not ENTITY_ID_PATTERN.match(entity_id):
|
if not valid_entity_id(entity_id):
|
||||||
raise InvalidEntityFormatError((
|
raise InvalidEntityFormatError((
|
||||||
"Invalid entity id encountered: {}. "
|
"Invalid entity id encountered: {}. "
|
||||||
"Format should be <domain>.<object_id>").format(entity_id))
|
"Format should be <domain>.<object_id>").format(entity_id))
|
||||||
@ -360,12 +357,12 @@ class State(object):
|
|||||||
@property
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
"""Domain of this state."""
|
"""Domain of this state."""
|
||||||
return util.split_entity_id(self.entity_id)[0]
|
return split_entity_id(self.entity_id)[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def object_id(self):
|
def object_id(self):
|
||||||
"""Object id of this state."""
|
"""Object id of this state."""
|
||||||
return util.split_entity_id(self.entity_id)[1]
|
return split_entity_id(self.entity_id)[1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
@ -3,42 +3,7 @@ Helper methods for components within Home Assistant.
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.const import CONF_PLATFORM
|
||||||
from homeassistant.const import (
|
|
||||||
ATTR_ENTITY_ID, CONF_PLATFORM, DEVICE_DEFAULT_NAME)
|
|
||||||
from homeassistant.util import ensure_unique_string, slugify
|
|
||||||
|
|
||||||
|
|
||||||
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
|
||||||
""" Generate a unique entity ID based on given entity IDs or used ids. """
|
|
||||||
name = name.lower() or DEVICE_DEFAULT_NAME.lower()
|
|
||||||
if current_ids is None:
|
|
||||||
if hass is None:
|
|
||||||
raise RuntimeError("Missing required parameter currentids or hass")
|
|
||||||
|
|
||||||
current_ids = hass.states.entity_ids()
|
|
||||||
|
|
||||||
return ensure_unique_string(
|
|
||||||
entity_id_format.format(slugify(name.lower())), current_ids)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_entity_ids(hass, service):
|
|
||||||
"""
|
|
||||||
Helper method to extract a list of entity ids from a service call.
|
|
||||||
Will convert group entity ids to the entity ids it represents.
|
|
||||||
"""
|
|
||||||
if not (service.data and ATTR_ENTITY_ID in service.data):
|
|
||||||
return []
|
|
||||||
|
|
||||||
group = get_component('group')
|
|
||||||
|
|
||||||
# Entity ID attr can be a list or a string
|
|
||||||
service_ent_id = service.data[ATTR_ENTITY_ID]
|
|
||||||
|
|
||||||
if isinstance(service_ent_id, str):
|
|
||||||
return group.expand_entity_ids(hass, [service_ent_id])
|
|
||||||
|
|
||||||
return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)]
|
|
||||||
|
|
||||||
|
|
||||||
def validate_config(config, items, logger):
|
def validate_config(config, items, logger):
|
||||||
|
@ -6,8 +6,10 @@ Provides ABC for entities in HA.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import re
|
||||||
|
|
||||||
from homeassistant.exceptions import NoEntitySpecifiedError
|
from homeassistant.exceptions import NoEntitySpecifiedError
|
||||||
|
from homeassistant.util import ensure_unique_string, slugify
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON,
|
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON,
|
||||||
@ -17,6 +19,32 @@ from homeassistant.const import (
|
|||||||
# Dict mapping entity_id to a boolean that overwrites the hidden property
|
# Dict mapping entity_id to a boolean that overwrites the hidden property
|
||||||
_OVERWRITE = defaultdict(dict)
|
_OVERWRITE = defaultdict(dict)
|
||||||
|
|
||||||
|
# Pattern for validating entity IDs (format: <domain>.<entity>)
|
||||||
|
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
||||||
|
""" Generate a unique entity ID based on given entity IDs or used ids. """
|
||||||
|
name = name.lower() or DEVICE_DEFAULT_NAME.lower()
|
||||||
|
if current_ids is None:
|
||||||
|
if hass is None:
|
||||||
|
raise RuntimeError("Missing required parameter currentids or hass")
|
||||||
|
|
||||||
|
current_ids = hass.states.entity_ids()
|
||||||
|
|
||||||
|
return ensure_unique_string(
|
||||||
|
entity_id_format.format(slugify(name.lower())), current_ids)
|
||||||
|
|
||||||
|
|
||||||
|
def split_entity_id(entity_id):
|
||||||
|
""" Splits a state entity_id into domain, object_id. """
|
||||||
|
return entity_id.split(".", 1)
|
||||||
|
|
||||||
|
|
||||||
|
def valid_entity_id(entity_id):
|
||||||
|
"""Test if an entity ID is a valid format."""
|
||||||
|
return ENTITY_ID_PATTERN.match(entity_id) is not None
|
||||||
|
|
||||||
|
|
||||||
class Entity(object):
|
class Entity(object):
|
||||||
""" ABC for Home Assistant entities. """
|
""" ABC for Home Assistant entities. """
|
||||||
@ -175,3 +203,10 @@ class ToggleEntity(Entity):
|
|||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" Turn the entity off. """
|
""" Turn the entity off. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def toggle(self, **kwargs):
|
||||||
|
""" Toggle the entity off. """
|
||||||
|
if self.is_on:
|
||||||
|
self.turn_off(**kwargs)
|
||||||
|
else:
|
||||||
|
self.turn_on(**kwargs)
|
||||||
|
@ -7,9 +7,10 @@ Provides helpers for components that manage entities.
|
|||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from homeassistant.bootstrap import prepare_setup_platform
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import config_per_platform
|
||||||
generate_entity_id, config_per_platform, extract_entity_ids)
|
from homeassistant.helpers.entity import generate_entity_id
|
||||||
from homeassistant.helpers.event import track_utc_time_change
|
from homeassistant.helpers.event import track_utc_time_change
|
||||||
|
from homeassistant.helpers.service import extract_entity_ids
|
||||||
from homeassistant.components import group, discovery
|
from homeassistant.components import group, discovery
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Helpers for listening to events
|
Helpers for listening to events
|
||||||
"""
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
import functools as ft
|
import functools as ft
|
||||||
|
|
||||||
from ..util import dt as dt_util
|
from ..util import dt as dt_util
|
||||||
@ -95,6 +96,54 @@ def track_point_in_utc_time(hass, action, point_in_time):
|
|||||||
return point_in_time_listener
|
return point_in_time_listener
|
||||||
|
|
||||||
|
|
||||||
|
def track_sunrise(hass, action, offset=None):
|
||||||
|
"""
|
||||||
|
Adds a listener that will fire a specified offset from sunrise daily.
|
||||||
|
"""
|
||||||
|
from homeassistant.components import sun
|
||||||
|
offset = offset or timedelta()
|
||||||
|
|
||||||
|
def next_rise():
|
||||||
|
""" Returns next sunrise. """
|
||||||
|
next_time = sun.next_rising_utc(hass) + offset
|
||||||
|
|
||||||
|
while next_time < dt_util.utcnow():
|
||||||
|
next_time = next_time + timedelta(days=1)
|
||||||
|
|
||||||
|
return next_time
|
||||||
|
|
||||||
|
def sunrise_automation_listener(now):
|
||||||
|
""" Called when it's time for action. """
|
||||||
|
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
||||||
|
action()
|
||||||
|
|
||||||
|
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
||||||
|
|
||||||
|
|
||||||
|
def track_sunset(hass, action, offset=None):
|
||||||
|
"""
|
||||||
|
Adds a listener that will fire a specified offset from sunset daily.
|
||||||
|
"""
|
||||||
|
from homeassistant.components import sun
|
||||||
|
offset = offset or timedelta()
|
||||||
|
|
||||||
|
def next_set():
|
||||||
|
""" Returns next sunrise. """
|
||||||
|
next_time = sun.next_setting_utc(hass) + offset
|
||||||
|
|
||||||
|
while next_time < dt_util.utcnow():
|
||||||
|
next_time = next_time + timedelta(days=1)
|
||||||
|
|
||||||
|
return next_time
|
||||||
|
|
||||||
|
def sunset_automation_listener(now):
|
||||||
|
""" Called when it's time for action. """
|
||||||
|
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||||
|
action()
|
||||||
|
|
||||||
|
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
||||||
hour=None, minute=None, second=None, local=False):
|
hour=None, minute=None, second=None, local=False):
|
||||||
@ -121,7 +170,6 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
|||||||
|
|
||||||
if local:
|
if local:
|
||||||
now = dt_util.as_local(now)
|
now = dt_util.as_local(now)
|
||||||
|
|
||||||
mat = _matcher
|
mat = _matcher
|
||||||
|
|
||||||
# pylint: disable=too-many-boolean-expressions
|
# pylint: disable=too-many-boolean-expressions
|
||||||
@ -150,6 +198,8 @@ def _process_match_param(parameter):
|
|||||||
""" Wraps parameter in a tuple if it is not one and returns it. """
|
""" Wraps parameter in a tuple if it is not one and returns it. """
|
||||||
if parameter is None or parameter == MATCH_ALL:
|
if parameter is None or parameter == MATCH_ALL:
|
||||||
return MATCH_ALL
|
return MATCH_ALL
|
||||||
|
elif isinstance(parameter, str) and parameter.startswith('/'):
|
||||||
|
return parameter
|
||||||
elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
|
elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
|
||||||
return (parameter,)
|
return (parameter,)
|
||||||
else:
|
else:
|
||||||
@ -161,4 +211,10 @@ def _matcher(subject, pattern):
|
|||||||
|
|
||||||
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
|
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(pattern, str) and pattern.startswith('/'):
|
||||||
|
try:
|
||||||
|
return subject % float(pattern.lstrip('/')) == 0
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
return MATCH_ALL == pattern or subject in pattern
|
return MATCH_ALL == pattern or subject in pattern
|
||||||
|
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