Merge pull request #1001 from balloob/dev

0.12
This commit is contained in:
Paulus Schoutsen 2016-01-29 22:37:38 -08:00
commit d0bcec12b9
129 changed files with 4246 additions and 1014 deletions

View File

@ -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
View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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

View 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

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

View File

@ -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/

View 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

View File

@ -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"

View File

@ -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(

View File

@ -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):

View File

@ -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():

View File

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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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"

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "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

View File

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

View File

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

View File

@ -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

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

View File

@ -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

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

View File

@ -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

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

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

View File

@ -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()

View File

@ -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

View File

@ -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):

View 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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -20,7 +20,7 @@ from homeassistant.components.media_player import (
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
REQUIREMENTS = ['pychromecast==0.6.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 | \

View File

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

View File

@ -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

View File

@ -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()

View File

@ -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 = {}

View File

@ -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.')

View File

@ -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. """

View File

@ -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:

View File

@ -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

View 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

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

View File

@ -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()

View File

@ -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):

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

View File

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

View File

@ -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,

View File

@ -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. """

View File

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

View File

@ -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))])

View File

@ -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 = {

View File

@ -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

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

View File

@ -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. """

View 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

View File

@ -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:

View File

@ -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'],

View File

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

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

View File

@ -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):

View File

@ -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':

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

View File

@ -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

View 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

View File

@ -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"

View File

@ -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. """

View File

@ -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

View 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

View File

@ -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__)

View File

@ -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. """

View File

@ -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())

View 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

View File

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

View File

@ -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. """

View File

@ -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. """

View File

@ -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:

View File

@ -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

View File

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

View File

@ -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),

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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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"

View File

@ -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):

View File

@ -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):

View File

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

View File

@ -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

View File

@ -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