Merge pull request #11921 from home-assistant/release-0-62

0.62
This commit is contained in:
Paulus Schoutsen 2018-01-26 21:07:57 -08:00 committed by GitHub
commit 5bde72d490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
322 changed files with 9662 additions and 7205 deletions

View File

@ -97,6 +97,9 @@ omit =
homeassistant/components/homematic/__init__.py homeassistant/components/homematic/__init__.py
homeassistant/components/*/homematic.py homeassistant/components/*/homematic.py
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
homeassistant/components/insteon_local.py homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py homeassistant/components/*/insteon_local.py
@ -106,6 +109,9 @@ omit =
homeassistant/components/ios.py homeassistant/components/ios.py
homeassistant/components/*/ios.py homeassistant/components/*/ios.py
homeassistant/components/iota.py
homeassistant/components/*/iota.py
homeassistant/components/isy994.py homeassistant/components/isy994.py
homeassistant/components/*/isy994.py homeassistant/components/*/isy994.py
@ -145,6 +151,9 @@ omit =
homeassistant/components/modbus.py homeassistant/components/modbus.py
homeassistant/components/*/modbus.py homeassistant/components/*/modbus.py
homeassistant/components/mychevy.py
homeassistant/components/*/mychevy.py
homeassistant/components/mysensors.py homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py homeassistant/components/*/mysensors.py
@ -241,6 +250,9 @@ omit =
homeassistant/components/volvooncall.py homeassistant/components/volvooncall.py
homeassistant/components/*/volvooncall.py homeassistant/components/*/volvooncall.py
homeassistant/components/waterfurnace.py
homeassistant/components/*/waterfurnace.py
homeassistant/components/*/webostv.py homeassistant/components/*/webostv.py
homeassistant/components/wemo.py homeassistant/components/wemo.py
@ -304,6 +316,7 @@ omit =
homeassistant/components/camera/ring.py homeassistant/components/camera/ring.py
homeassistant/components/camera/rpi_camera.py homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py homeassistant/components/camera/synology.py
homeassistant/components/camera/xeoma.py
homeassistant/components/camera/yi.py homeassistant/components/camera/yi.py
homeassistant/components/climate/econet.py homeassistant/components/climate/econet.py
homeassistant/components/climate/ephember.py homeassistant/components/climate/ephember.py
@ -318,6 +331,7 @@ omit =
homeassistant/components/climate/radiotherm.py homeassistant/components/climate/radiotherm.py
homeassistant/components/climate/sensibo.py homeassistant/components/climate/sensibo.py
homeassistant/components/climate/touchline.py homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py
homeassistant/components/cover/garadget.py homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py homeassistant/components/cover/homematic.py
homeassistant/components/cover/knx.py homeassistant/components/cover/knx.py
@ -327,7 +341,6 @@ omit =
homeassistant/components/cover/scsgate.py homeassistant/components/cover/scsgate.py
homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/automatic.py homeassistant/components/device_tracker/automatic.py
homeassistant/components/device_tracker/bbox.py homeassistant/components/device_tracker/bbox.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py homeassistant/components/device_tracker/bluetooth_le_tracker.py
@ -504,6 +517,7 @@ omit =
homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/blockchain.py homeassistant/components/sensor/blockchain.py
homeassistant/components/sensor/bme280.py homeassistant/components/sensor/bme280.py
homeassistant/components/sensor/bme680.py
homeassistant/components/sensor/bom.py homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py homeassistant/components/sensor/buienradar.py
@ -606,6 +620,7 @@ omit =
homeassistant/components/sensor/sytadin.py homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/teksavvy.py
homeassistant/components/sensor/temper.py homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py homeassistant/components/sensor/time_date.py

13
.github/move.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Configuration for move-issues - https://github.com/dessant/move-issues
# Delete the command comment. Ignored when the comment also contains other content
deleteCommand: true
# Close the source issue after moving
closeSourceIssue: true
# Lock the source issue after moving
lockSourceIssue: false
# Set custom aliases for targets
# aliases:
# r: repo
# or: owner/repo

View File

@ -32,12 +32,15 @@ homeassistant/components/zone.py @home-assistant/core
# To monitor non-pypi additions # To monitor non-pypi additions
requirements_all.txt @andrey-git requirements_all.txt @andrey-git
# HomeAssistant developer Teams
Dockerfile @home-assistant/docker Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker virtualization/Docker/* @home-assistant/docker
homeassistant/components/zwave/* @home-assistant/z-wave homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave homeassistant/components/*/zwave.py @home-assistant/z-wave
homeassistant/components/hassio.py @home-assistant/hassio
# Indiviudal components # Indiviudal components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya homeassistant/components/camera/yi.py @bachya

View File

@ -1,9 +1,8 @@
""" """
ADS Component. Support for Automation Device Specification (ADS).
For more details about this component, please refer to the documentation. For more details about this component, please refer to the documentation.
https://home-assistant.io/components/ads/ https://home-assistant.io/components/ads/
""" """
import threading import threading
import struct import struct
@ -29,7 +28,6 @@ ADSTYPE_BOOL = 'bool'
DOMAIN = 'ads' DOMAIN = 'ads'
# config variable names
CONF_ADS_VAR = 'adsvar' CONF_ADS_VAR = 'adsvar'
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness' CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
CONF_ADS_TYPE = 'adstype' CONF_ADS_TYPE = 'adstype'
@ -47,10 +45,10 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({ SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
vol.Required(CONF_ADS_TYPE):
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]),
vol.Required(CONF_ADS_VALUE): cv.match_all,
vol.Required(CONF_ADS_VAR): cv.string, vol.Required(CONF_ADS_VAR): cv.string,
vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT,
ADSTYPE_BYTE]),
vol.Required(CONF_ADS_VALUE): cv.match_all
}) })
@ -59,15 +57,12 @@ def setup(hass, config):
import pyads import pyads
conf = config[DOMAIN] conf = config[DOMAIN]
# get ads connection parameters from config
net_id = conf.get(CONF_DEVICE) net_id = conf.get(CONF_DEVICE)
ip_address = conf.get(CONF_IP_ADDRESS) ip_address = conf.get(CONF_IP_ADDRESS)
port = conf.get(CONF_PORT) port = conf.get(CONF_PORT)
# create a new ads connection
client = pyads.Connection(net_id, port, ip_address) client = pyads.Connection(net_id, port, ip_address)
# add some constants to AdsHub
AdsHub.ADS_TYPEMAP = { AdsHub.ADS_TYPEMAP = {
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL, ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE, ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
@ -81,16 +76,13 @@ def setup(hass, config):
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT
AdsHub.ADSError = pyads.ADSError AdsHub.ADSError = pyads.ADSError
# connect to ads client and try to connect
try: try:
ads = AdsHub(client) ads = AdsHub(client)
except pyads.pyads.ADSError: except pyads.pyads.ADSError:
_LOGGER.error( _LOGGER.error(
'Could not connect to ADS host (netid=%s, port=%s)', net_id, port "Could not connect to ADS host (netid=%s, port=%s)", net_id, port)
)
return False return False
# add ads hub to hass data collection, listen to shutdown
hass.data[DATA_ADS] = ads hass.data[DATA_ADS] = ads
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown) hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown)
@ -107,43 +99,41 @@ def setup(hass, config):
hass.services.register( hass.services.register(
DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name, DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name,
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME)
)
return True return True
# tuple to hold data needed for notification # Tuple to hold data needed for notification
NotificationItem = namedtuple( NotificationItem = namedtuple(
'NotificationItem', 'hnotify huser name plc_datatype callback' 'NotificationItem', 'hnotify huser name plc_datatype callback'
) )
class AdsHub: class AdsHub(object):
"""Representation of a PyADS connection.""" """Representation of an ADS connection."""
def __init__(self, ads_client): def __init__(self, ads_client):
"""Initialize the ADS Hub.""" """Initialize the ADS hub."""
self._client = ads_client self._client = ads_client
self._client.open() self._client.open()
# all ADS devices are registered here # All ADS devices are registered here
self._devices = [] self._devices = []
self._notification_items = {} self._notification_items = {}
self._lock = threading.Lock() self._lock = threading.Lock()
def shutdown(self, *args, **kwargs): def shutdown(self, *args, **kwargs):
"""Shutdown ADS connection.""" """Shutdown ADS connection."""
_LOGGER.debug('Shutting down ADS') _LOGGER.debug("Shutting down ADS")
for notification_item in self._notification_items.values(): for notification_item in self._notification_items.values():
self._client.del_device_notification( self._client.del_device_notification(
notification_item.hnotify, notification_item.hnotify,
notification_item.huser notification_item.huser
) )
_LOGGER.debug( _LOGGER.debug(
'Deleting device notification %d, %d', "Deleting device notification %d, %d",
notification_item.hnotify, notification_item.huser notification_item.hnotify, notification_item.huser)
)
self._client.close() self._client.close()
def register_device(self, device): def register_device(self, device):
@ -167,33 +157,30 @@ class AdsHub:
with self._lock: with self._lock:
hnotify, huser = self._client.add_device_notification( hnotify, huser = self._client.add_device_notification(
name, attr, self._device_notification_callback name, attr, self._device_notification_callback)
)
hnotify = int(hnotify) hnotify = int(hnotify)
_LOGGER.debug( _LOGGER.debug(
'Added Device Notification %d for variable %s', hnotify, name "Added device notification %d for variable %s", hnotify, name)
)
self._notification_items[hnotify] = NotificationItem( self._notification_items[hnotify] = NotificationItem(
hnotify, huser, name, plc_datatype, callback hnotify, huser, name, plc_datatype, callback)
)
def _device_notification_callback(self, addr, notification, huser): def _device_notification_callback(self, addr, notification, huser):
"""Handle device notifications.""" """Handle device notifications."""
contents = notification.contents contents = notification.contents
hnotify = int(contents.hNotification) hnotify = int(contents.hNotification)
_LOGGER.debug('Received Notification %d', hnotify) _LOGGER.debug("Received notification %d", hnotify)
data = contents.data data = contents.data
try: try:
notification_item = self._notification_items[hnotify] notification_item = self._notification_items[hnotify]
except KeyError: except KeyError:
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify) _LOGGER.debug("Unknown device notification handle: %d", hnotify)
return return
# parse data to desired datatype # Parse data to desired datatype
if notification_item.plc_datatype == self.PLCTYPE_BOOL: if notification_item.plc_datatype == self.PLCTYPE_BOOL:
value = bool(struct.unpack('<?', bytearray(data)[:1])[0]) value = bool(struct.unpack('<?', bytearray(data)[:1])[0])
elif notification_item.plc_datatype == self.PLCTYPE_INT: elif notification_item.plc_datatype == self.PLCTYPE_INT:
@ -204,7 +191,6 @@ class AdsHub:
value = struct.unpack('<H', bytearray(data)[:2])[0] value = struct.unpack('<H', bytearray(data)[:2])[0]
else: else:
value = bytearray(data) value = bytearray(data)
_LOGGER.warning('No callback available for this datatype.') _LOGGER.warning("No callback available for this datatype")
# execute callback
notification_item.callback(notification_item.name, value) notification_item.callback(notification_item.name, value)

View File

@ -6,12 +6,12 @@ https://home-assistant.io/components/alarm_control_panel.abode/
""" """
import logging import logging
from homeassistant.components.abode import ( from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
AbodeDevice, DOMAIN as ABODE_DOMAIN, CONF_ATTRIBUTION) from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
from homeassistant.components.alarm_control_panel import (AlarmControlPanel) from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.const import (ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
DEPENDENCIES = ['abode'] DEPENDENCIES = ['abode']
@ -21,7 +21,7 @@ ICON = 'mdi:security'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for an Abode device.""" """Set up an alarm control panel for an Abode device."""
data = hass.data[ABODE_DOMAIN] data = hass.data[ABODE_DOMAIN]
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)] alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
@ -41,7 +41,7 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
@property @property
def icon(self): def icon(self):
"""Return icon.""" """Return the icon."""
return ICON return ICON
@property @property
@ -81,5 +81,5 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'device_id': self._device.device_id, 'device_id': self._device.device_id,
'battery_backup': self._device.battery, 'battery_backup': self._device.battery,
'cellular_backup': self._device.is_cellular 'cellular_backup': self._device.is_cellular,
} }

View File

@ -10,12 +10,11 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv from homeassistant.components.alarmdecoder import DATA_AD, SIGNAL_PANEL_MESSAGE
from homeassistant.components.alarmdecoder import (
DATA_AD, SIGNAL_PANEL_MESSAGE)
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -67,6 +66,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
SIGNAL_PANEL_MESSAGE, self._message_callback) SIGNAL_PANEL_MESSAGE, self._message_callback)
def _message_callback(self, message): def _message_callback(self, message):
"""Handle received messages."""
if message.alarm_sounding or message.fire_alarm: if message.alarm_sounding or message.fire_alarm:
self._state = STATE_ALARM_TRIGGERED self._state = STATE_ALARM_TRIGGERED
elif message.armed_away: elif message.armed_away:
@ -120,7 +120,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
'entry_delay_off': self._entry_delay_off, 'entry_delay_off': self._entry_delay_off,
'programming_mode': self._programming_mode, 'programming_mode': self._programming_mode,
'ready': self._ready, 'ready': self._ready,
'zone_bypassed': self._zone_bypassed 'zone_bypassed': self._zone_bypassed,
} }
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):

View File

@ -4,17 +4,18 @@ Interfaces with Alarm.com alarm control panels.
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/alarm_control_panel.alarmdotcom/ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
""" """
import logging
import asyncio import asyncio
import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyalarmdotcom==0.3.0'] REQUIREMENTS = ['pyalarmdotcom==0.3.0']
@ -44,7 +45,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class AlarmDotCom(alarm.AlarmControlPanel): class AlarmDotCom(alarm.AlarmControlPanel):
"""Represent an Alarm.com status.""" """Representation of an Alarm.com status."""
def __init__(self, hass, name, code, username, password): def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status.""" """Initialize the Alarm.com status."""
@ -57,10 +58,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._password = password self._password = password
self._websession = async_get_clientsession(self._hass) self._websession = async_get_clientsession(self._hass)
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._alarm = Alarmdotcom(username, self._alarm = Alarmdotcom(
password, username, password, self._websession, hass.loop)
self._websession,
hass.loop)
@asyncio.coroutine @asyncio.coroutine
def async_login(self): def async_login(self):
@ -80,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property @property
def code_format(self): def code_format(self):
"""One or more characters if code is defined.""" """Return one or more characters if code is defined."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
@property @property
@ -116,5 +115,5 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Validate given code.""" """Validate given code."""
check = self._code is None or code == self._code check = self._code is None or code == self._code
if not check: if not check:
_LOGGER.warning('Wrong code entered.') _LOGGER.warning("Wrong code entered")
return check return check

View File

@ -29,9 +29,9 @@ DEFAULT_PORT = 5007
SCAN_INTERVAL = timedelta(seconds=1) SCAN_INTERVAL = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}) })
@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([Concord232Alarm(hass, url, name)]) add_devices([Concord232Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex: except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex)) _LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False return
class Concord232Alarm(alarm.AlarmControlPanel): class Concord232Alarm(alarm.AlarmControlPanel):
@ -107,7 +107,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
newstate = STATE_ALARM_ARMED_AWAY newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state: if not newstate == self._state:
_LOGGER.info("State Change from %s to %s", self._state, newstate) _LOGGER.info("State change from %s to %s", self._state, newstate)
self._state = newstate self._state = newstate
return self._state return self._state

View File

@ -10,13 +10,13 @@ import requests
import voluptuous as vol import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED, EVENT_HOMEASSISTANT_STOP) STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pythonegardia==1.0.26'] REQUIREMENTS = ['pythonegardia==1.0.26']
@ -35,6 +35,7 @@ DEFAULT_REPORT_SERVER_PORT = 52010
DEFAULT_VERSION = 'GATE-01' DEFAULT_VERSION = 'GATE-01'
DOMAIN = 'egardia' DOMAIN = 'egardia'
D_EGARDIASRV = 'egardiaserver' D_EGARDIASRV = 'egardiaserver'
NOTIFICATION_ID = 'egardia_notification' NOTIFICATION_ID = 'egardia_notification'
NOTIFICATION_TITLE = 'Egardia' NOTIFICATION_TITLE = 'Egardia'
@ -97,8 +98,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
server = egardiaserver.EgardiaServer('', rs_port) server = egardiaserver.EgardiaServer('', rs_port)
bound = server.bind() bound = server.bind()
if not bound: if not bound:
raise IOError("Binding error occurred while " + raise IOError(
"starting EgardiaServer") "Binding error occurred while starting EgardiaServer")
hass.data[D_EGARDIASRV] = server hass.data[D_EGARDIASRV] = server
server.start() server.start()
except IOError: except IOError:
@ -106,22 +107,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[D_EGARDIASRV].register_callback(eg_dev.handle_status_event) hass.data[D_EGARDIASRV].register_callback(eg_dev.handle_status_event)
def handle_stop_event(event): def handle_stop_event(event):
"""Callback function for HA stop event.""" """Call function for Home Assistant stop event."""
hass.data[D_EGARDIASRV].stop() hass.data[D_EGARDIASRV].stop()
# listen to home assistant stop event
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
# add egardia alarm device
add_devices([eg_dev], True) add_devices([eg_dev], True)
class EgardiaAlarm(alarm.AlarmControlPanel): class EgardiaAlarm(alarm.AlarmControlPanel):
"""Representation of a Egardia alarm.""" """Representation of a Egardia alarm."""
def __init__(self, name, egardiasystem, def __init__(self, name, egardiasystem, rs_enabled=False, rs_codes=None):
rs_enabled=False, rs_codes=None): """Initialize the Egardia alarm."""
"""Initialize object."""
self._name = name self._name = name
self._egardiasystem = egardiasystem self._egardiasystem = egardiasystem
self._status = None self._status = None
@ -149,7 +147,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
return False return False
def handle_status_event(self, event): def handle_status_event(self, event):
"""Handle egardia_system_status_event.""" """Handle the Egardia system status event."""
statuscode = event.get('status') statuscode = event.get('status')
if statuscode is not None: if statuscode is not None:
status = self.lookupstatusfromcode(statuscode) status = self.lookupstatusfromcode(statuscode)

View File

@ -8,12 +8,12 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_HOST, STATE_ALARM_ARMED_AWAY, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, CONF_NAME) STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyialarm==0.2'] REQUIREMENTS = ['pyialarm==0.2']
@ -33,9 +33,9 @@ def no_application_protocol(value):
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}) })
@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class IAlarmPanel(alarm.AlarmControlPanel): class IAlarmPanel(alarm.AlarmControlPanel):
"""Represent an iAlarm status.""" """Representation of an iAlarm status."""
def __init__(self, name, username, password, url): def __init__(self, name, username, password, url):
"""Initialize the iAlarm status.""" """Initialize the iAlarm status."""

View File

@ -11,15 +11,17 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, CONF_CODE, CONF_DELAY_TIME, CONF_DISARM_AFTER_TRIGGER, CONF_NAME,
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, CONF_PENDING_TIME, CONF_PLATFORM, CONF_TRIGGER_TIME,
STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
CONF_DISARM_AFTER_TRIGGER) STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
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
_LOGGER = logging.getLogger(__name__)
CONF_CODE_TEMPLATE = 'code_template' CONF_CODE_TEMPLATE = 'code_template'
@ -44,6 +46,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config): def _state_validator(config):
"""Validate the state."""
config = copy.deepcopy(config) config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES: for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]: if CONF_DELAY_TIME not in config[state]:
@ -58,6 +61,7 @@ def _state_validator(config):
def _state_schema(state): def _state_schema(state):
"""Validate the state."""
schema = {} schema = {}
if state in SUPPORTED_PRETRIGGER_STATES: if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All( schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
@ -97,8 +101,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All({
_state_schema(STATE_ALARM_TRIGGERED), _state_schema(STATE_ALARM_TRIGGERED),
}, _state_validator)) }, _state_validator))
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the manual alarm platform.""" """Set up the manual alarm platform."""
@ -151,7 +153,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property @property
def should_poll(self): def should_poll(self):
"""Return the plling state.""" """Return the polling state."""
return False return False
@property @property
@ -182,23 +184,26 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property @property
def _active_state(self): def _active_state(self):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING: if self.state == STATE_ALARM_PENDING:
return self._previous_state return self._previous_state
else: else:
return self._state return self._state
def _pending_time(self, state): def _pending_time(self, state):
"""Get the pending time."""
pending_time = self._pending_time_by_state[state] pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED: if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state] pending_time += self._delay_time_by_state[self._previous_state]
return pending_time return pending_time
def _within_pending_time(self, state): def _within_pending_time(self, state):
"""Get if the action is in the pending time window."""
return self._state_ts + self._pending_time(state) > dt_util.utcnow() return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property @property
def code_format(self): def code_format(self):
"""One or more characters.""" """Return one or more characters."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
@ -250,6 +255,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_TRIGGERED) self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state): def _update_state(self, state):
"""Update the state."""
if self._state == state: if self._state == state:
return return

View File

@ -26,6 +26,8 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time from homeassistant.helpers.event import track_point_in_time
_LOGGER = logging.getLogger(__name__)
CONF_CODE_TEMPLATE = 'code_template' CONF_CODE_TEMPLATE = 'code_template'
CONF_PAYLOAD_DISARM = 'payload_disarm' CONF_PAYLOAD_DISARM = 'payload_disarm'
@ -58,6 +60,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config): def _state_validator(config):
"""Validate the state."""
config = copy.deepcopy(config) config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES: for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]: if CONF_DELAY_TIME not in config[state]:
@ -72,6 +75,7 @@ def _state_validator(config):
def _state_schema(state): def _state_schema(state):
"""Validate the state."""
schema = {} schema = {}
if state in SUPPORTED_PRETRIGGER_STATES: if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All( schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
@ -117,8 +121,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
}), _state_validator)) }), _state_validator))
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the manual MQTT alarm platform.""" """Set up the manual MQTT alarm platform."""
@ -150,11 +152,10 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
A trigger_time of zero disables the alarm_trigger service. A trigger_time of zero disables the alarm_trigger service.
""" """
def __init__(self, hass, name, code, code_template, def __init__(self, hass, name, code, code_template, disarm_after_trigger,
disarm_after_trigger, state_topic, command_topic, qos, payload_disarm,
state_topic, command_topic, qos, payload_arm_home, payload_arm_away, payload_arm_night,
payload_disarm, payload_arm_home, payload_arm_away, config):
payload_arm_night, config):
"""Init the manual MQTT alarm panel.""" """Init the manual MQTT alarm panel."""
self._state = STATE_ALARM_DISARMED self._state = STATE_ALARM_DISARMED
self._hass = hass self._hass = hass
@ -219,23 +220,26 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property @property
def _active_state(self): def _active_state(self):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING: if self.state == STATE_ALARM_PENDING:
return self._previous_state return self._previous_state
else: else:
return self._state return self._state
def _pending_time(self, state): def _pending_time(self, state):
"""Get the pending time."""
pending_time = self._pending_time_by_state[state] pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED: if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state] pending_time += self._delay_time_by_state[self._previous_state]
return pending_time return pending_time
def _within_pending_time(self, state): def _within_pending_time(self, state):
"""Get if the action is in the pending time window."""
return self._state_ts + self._pending_time(state) > dt_util.utcnow() return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property @property
def code_format(self): def code_format(self):
"""One or more characters.""" """Return one or more characters."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
@ -280,6 +284,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_TRIGGERED) self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state): def _update_state(self, state):
"""Update the state."""
if self._state == state: if self._state == state:
return return
@ -329,7 +334,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return state_attr return state_attr
def async_added_to_hass(self): def async_added_to_hass(self):
"""Subscribe mqtt events. """Subscribe to MQTT events.
This method must be run in the event loop and returns a coroutine. This method must be run in the event loop and returns a coroutine.
""" """
@ -358,5 +363,5 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@asyncio.coroutine @asyncio.coroutine
def _async_state_changed_listener(self, entity_id, old_state, new_state): def _async_state_changed_listener(self, entity_id, old_state, new_state):
"""Publish state change to MQTT.""" """Publish state change to MQTT."""
mqtt.async_publish(self.hass, self._state_topic, new_state.state, mqtt.async_publish(
self._qos, True) self.hass, self._state_topic, new_state.state, self._qos, True)

View File

@ -42,7 +42,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
}) }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine @asyncio.coroutine

View File

@ -12,8 +12,8 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT) STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.4'] REQUIREMENTS = ['pynx584==0.4']
@ -25,14 +25,14 @@ DEFAULT_NAME = 'NX584'
DEFAULT_PORT = 5007 DEFAULT_PORT = 5007
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the nx584 platform.""" """Set up the NX584 platform."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
host = config.get(CONF_HOST) host = config.get(CONF_HOST)
port = config.get(CONF_PORT) port = config.get(CONF_PORT)
@ -88,7 +88,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
zones = [] zones = []
except IndexError: except IndexError:
_LOGGER.error("nx584 reports no partitions") _LOGGER.error("NX584 reports no partitions")
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
zones = [] zones = []

View File

@ -8,9 +8,8 @@ import asyncio
import logging import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.satel_integra import (CONF_ARM_HOME_MODE, from homeassistant.components.satel_integra import (
DATA_SATEL, CONF_ARM_HOME_MODE, DATA_SATEL, SIGNAL_PANEL_MESSAGE)
SIGNAL_PANEL_MESSAGE)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -21,12 +20,12 @@ DEPENDENCIES = ['satel_integra']
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up for AlarmDecoder alarm panels.""" """Set up for Satel Integra alarm panels."""
if not discovery_info: if not discovery_info:
return return
device = SatelIntegraAlarmPanel("Alarm Panel", device = SatelIntegraAlarmPanel(
discovery_info.get(CONF_ARM_HOME_MODE)) "Alarm Panel", discovery_info.get(CONF_ARM_HOME_MODE))
async_add_devices([device]) async_add_devices([device])
@ -47,7 +46,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
@callback @callback
def _message_callback(self, message): def _message_callback(self, message):
"""Handle received messages."""
if message != self._state: if message != self._state:
self._state = message self._state = message
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@ -90,5 +89,5 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
def async_alarm_arm_home(self, code=None): def async_alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
if code: if code:
yield from self.hass.data[DATA_SATEL].arm(code, yield from self.hass.data[DATA_SATEL].arm(
self._arm_home_mode) code, self._arm_home_mode)

View File

@ -11,9 +11,9 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME, CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
EVENT_HOMEASSISTANT_STOP) STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['simplisafe-python==1.0.5'] REQUIREMENTS = ['simplisafe-python==1.0.5']
@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe' DEFAULT_NAME = 'SimpliSafe'
DOMAIN = 'simplisafe' DOMAIN = 'simplisafe'
NOTIFICATION_ID = 'simplisafe_notification' NOTIFICATION_ID = 'simplisafe_notification'
NOTIFICATION_TITLE = 'SimpliSafe Setup' NOTIFICATION_TITLE = 'SimpliSafe Setup'
@ -65,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SimpliSafeAlarm(alarm.AlarmControlPanel): class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Representation a SimpliSafe alarm.""" """Representation of a SimpliSafe alarm."""
def __init__(self, simplisafe, name, code): def __init__(self, simplisafe, name, code):
"""Initialize the SimpliSafe alarm.""" """Initialize the SimpliSafe alarm."""
@ -82,7 +83,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property @property
def code_format(self): def code_format(self):
"""One or more characters if code is defined.""" """Return one or more characters if code is defined."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
@property @property
@ -103,12 +104,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
return { return {
'temperature': self.simplisafe.temperature(), 'alarm': self.simplisafe.alarm(),
'co': self.simplisafe.carbon_monoxide(), 'co': self.simplisafe.carbon_monoxide(),
'fire': self.simplisafe.fire(), 'fire': self.simplisafe.fire(),
'alarm': self.simplisafe.alarm(), 'flood': self.simplisafe.flood(),
'last_event': self.simplisafe.last_event(), 'last_event': self.simplisafe.last_event(),
'flood': self.simplisafe.flood() 'temperature': self.simplisafe.temperature(),
} }
def update(self): def update(self):

View File

@ -9,26 +9,27 @@ import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.spc import ( from homeassistant.components.spc import (
SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY) ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway)
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN) STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED, SPC_AREA_MODE_TO_STATE = {
'0': STATE_ALARM_DISARMED,
'1': STATE_ALARM_ARMED_HOME, '1': STATE_ALARM_ARMED_HOME,
'3': STATE_ALARM_ARMED_AWAY} '3': STATE_ALARM_ARMED_AWAY,
}
def _get_alarm_state(spc_mode): def _get_alarm_state(spc_mode):
"""Get the alarm state."""
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN) return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
discovery_info=None):
"""Set up the SPC alarm control panel platform.""" """Set up the SPC alarm control panel platform."""
if (discovery_info is None or if (discovery_info is None or
discovery_info[ATTR_DISCOVER_AREAS] is None): discovery_info[ATTR_DISCOVER_AREAS] is None):
@ -42,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices,
class SpcAlarm(alarm.AlarmControlPanel): class SpcAlarm(alarm.AlarmControlPanel):
"""Represents the SPC alarm panel.""" """Representation of the SPC alarm panel."""
def __init__(self, api, area): def __init__(self, api, area):
"""Initialize the SPC alarm panel.""" """Initialize the SPC alarm panel."""
@ -57,7 +58,7 @@ class SpcAlarm(alarm.AlarmControlPanel):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Calbback for init handlers.""" """Call for adding new entities."""
self.hass.data[DATA_REGISTRY].register_alarm_device( self.hass.data[DATA_REGISTRY].register_alarm_device(
self._area_id, self) self._area_id, self)

View File

@ -8,8 +8,8 @@ import logging
from time import sleep from time import sleep
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
from homeassistant.components.verisure import HUB as hub from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import (CONF_ALARM, CONF_CODE_DIGITS)
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN) STATE_UNKNOWN)
@ -43,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
"""Representation of a Verisure alarm status.""" """Representation of a Verisure alarm status."""
def __init__(self): def __init__(self):
"""Initalize the Verisure alarm panel.""" """Initialize the Verisure alarm panel."""
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._digits = hub.config.get(CONF_CODE_DIGITS) self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None self._changed_by = None

View File

@ -8,11 +8,10 @@ import asyncio
import logging import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (STATE_UNKNOWN, from homeassistant.components.wink import DOMAIN, WinkDevice
STATE_ALARM_DISARMED, from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_AWAY) STATE_UNKNOWN)
from homeassistant.components.wink import WinkDevice, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,7 +40,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Callback when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self) self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
@property @property

View File

@ -10,18 +10,33 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter
from . import flash_briefings, intent, smart_home
from .const import ( from .const import (
DOMAIN, CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL) CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
from . import flash_briefings, intent CONF_FILTER, CONF_ENTITY_CONFIG)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_FLASH_BRIEFINGS = 'flash_briefings'
CONF_SMART_HOME = 'smart_home'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
CONF_FLASH_BRIEFINGS = 'flash_briefings' ALEXA_ENTITY_SCHEMA = vol.Schema({
vol.Optional(smart_home.CONF_DESCRIPTION): cv.string,
vol.Optional(smart_home.CONF_DISPLAY_CATEGORIES): cv.string,
vol.Optional(smart_home.CONF_NAME): cv.string,
})
SMART_HOME_SCHEMA = vol.Schema({
vol.Optional(
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: { DOMAIN: {
@ -33,7 +48,10 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_TEXT, default=""): cv.template, vol.Required(CONF_TEXT, default=""): cv.template,
vol.Optional(CONF_DISPLAY_URL): cv.template, vol.Optional(CONF_DISPLAY_URL): cv.template,
}]), }]),
} },
# vol.Optional here would mean we couldn't distinguish between an empty
# smart_home: and none at all.
CONF_SMART_HOME: vol.Any(SMART_HOME_SCHEMA, None),
} }
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -49,4 +67,12 @@ def async_setup(hass, config):
if flash_briefings_config: if flash_briefings_config:
flash_briefings.async_setup(hass, flash_briefings_config) flash_briefings.async_setup(hass, flash_briefings_config)
try:
smart_home_config = config[CONF_SMART_HOME]
except KeyError:
pass
else:
smart_home_config = smart_home_config or SMART_HOME_SCHEMA({})
smart_home.async_setup(hass, smart_home_config)
return True return True

View File

@ -8,6 +8,9 @@ CONF_AUDIO = 'audio'
CONF_TEXT = 'text' CONF_TEXT = 'text'
CONF_DISPLAY_URL = 'display_url' CONF_DISPLAY_URL = 'display_url'
CONF_FILTER = 'filter'
CONF_ENTITY_CONFIG = 'entity_config'
ATTR_UID = 'uid' ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate' ATTR_UPDATE_DATE = 'updateDate'
ATTR_TITLE_TEXT = 'titleText' ATTR_TITLE_TEXT = 'titleText'

View File

@ -5,19 +5,18 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/ https://home-assistant.io/components/alexa/
""" """
import copy import copy
import logging
from datetime import datetime from datetime import datetime
import logging
import uuid import uuid
from homeassistant.components import http
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.components import http
from .const import ( from .const import (
CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL, ATTR_UID, ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT,
ATTR_UPDATE_DATE, ATTR_TITLE_TEXT, ATTR_STREAM_URL, ATTR_MAIN_TEXT, ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT,
ATTR_REDIRECTION_URL, DATE_FORMAT) CONF_TITLE, CONF_UID, DATE_FORMAT)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -46,11 +45,11 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
@callback @callback
def get(self, request, briefing_id): def get(self, request, briefing_id):
"""Handle Alexa Flash Briefing request.""" """Handle Alexa Flash Briefing request."""
_LOGGER.debug('Received Alexa flash briefing request for: %s', _LOGGER.debug("Received Alexa flash briefing request for: %s",
briefing_id) briefing_id)
if self.flash_briefings.get(briefing_id) is None: if self.flash_briefings.get(briefing_id) is None:
err = 'No configured Alexa flash briefing was found for: %s' err = "No configured Alexa flash briefing was found for: %s"
_LOGGER.error(err, briefing_id) _LOGGER.error(err, briefing_id)
return b'', 404 return b'', 404

View File

@ -3,30 +3,31 @@ Support for Alexa skill service end point.
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/alexa/ https://home-assistant.io/components/alexa/
""" """
import asyncio import asyncio
import enum import enum
import logging import logging
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback
from homeassistant.helpers import intent
from homeassistant.components import http from homeassistant.components import http
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
from .const import DOMAIN, SYN_RESOLUTION_MATCH from .const import DOMAIN, SYN_RESOLUTION_MATCH
INTENTS_API_ENDPOINT = '/api/alexa'
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HANDLERS = Registry()
INTENTS_API_ENDPOINT = '/api/alexa'
class SpeechType(enum.Enum): class SpeechType(enum.Enum):
"""The Alexa speech types.""" """The Alexa speech types."""
plaintext = "PlainText" plaintext = 'PlainText'
ssml = "SSML" ssml = 'SSML'
SPEECH_MAPPINGS = { SPEECH_MAPPINGS = {
@ -38,8 +39,8 @@ SPEECH_MAPPINGS = {
class CardType(enum.Enum): class CardType(enum.Enum):
"""The Alexa card types.""" """The Alexa card types."""
simple = "Simple" simple = 'Simple'
link_account = "LinkAccount" link_account = 'LinkAccount'
@callback @callback
@ -64,7 +65,7 @@ class AlexaIntentsView(http.HomeAssistantView):
hass = request.app['hass'] hass = request.app['hass']
message = yield from request.json() message = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', message) _LOGGER.debug("Received Alexa request: %s", message)
try: try:
response = yield from async_handle_message(hass, message) response = yield from async_handle_message(hass, message)
@ -81,7 +82,7 @@ class AlexaIntentsView(http.HomeAssistantView):
"This intent is not yet configured within Home Assistant.")) "This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err: except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err) _LOGGER.error("Received invalid slot data from Alexa: %s", err)
return self.json(intent_error_response( return self.json(intent_error_response(
hass, message, hass, message,
"Invalid slot information received for this intent.")) "Invalid slot information received for this intent."))
@ -109,6 +110,7 @@ def async_handle_message(hass, message):
- intent.UnknownIntent - intent.UnknownIntent
- intent.InvalidSlotInfo - intent.InvalidSlotInfo
- intent.IntentError - intent.IntentError
""" """
req = message.get('request') req = message.get('request')
req_type = req['type'] req_type = req['type']
@ -138,6 +140,7 @@ def async_handle_intent(hass, message):
- intent.UnknownIntent - intent.UnknownIntent
- intent.InvalidSlotInfo - intent.InvalidSlotInfo
- intent.IntentError - intent.IntentError
""" """
req = message.get('request') req = message.get('request')
alexa_intent_info = req.get('intent') alexa_intent_info = req.get('intent')

View File

@ -2,75 +2,374 @@
import asyncio import asyncio
import logging import logging
import math import math
from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from homeassistant.components import (
alert, automation, cover, fan, group, input_boolean, light, lock,
media_player, scene, script, switch, http, sensor)
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_LOCK, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_NAME, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET) SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
from homeassistant.components import ( CONF_UNIT_OF_MEASUREMENT)
alert, automation, cover, fan, group, input_boolean, light, lock, from .const import CONF_FILTER, CONF_ENTITY_CONFIG
media_player, scene, script, switch)
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
API_DIRECTIVE = 'directive' API_DIRECTIVE = 'directive'
API_ENDPOINT = 'endpoint' API_ENDPOINT = 'endpoint'
API_EVENT = 'event' API_EVENT = 'event'
API_CONTEXT = 'context'
API_HEADER = 'header' API_HEADER = 'header'
API_PAYLOAD = 'payload' API_PAYLOAD = 'payload'
API_TEMP_UNITS = {
TEMP_FAHRENHEIT: 'FAHRENHEIT',
TEMP_CELSIUS: 'CELSIUS',
}
SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home'
CONF_DESCRIPTION = 'description' CONF_DESCRIPTION = 'description'
CONF_DISPLAY_CATEGORIES = 'display_categories' CONF_DISPLAY_CATEGORIES = 'display_categories'
CONF_NAME = 'name'
HANDLERS = Registry()
MAPPING_COMPONENT = { class _DisplayCategory(object):
alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None], """Possible display categories for Discovery response.
automation.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
cover.DOMAIN: [ https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
'DOOR', ('Alexa.PowerController',), { """
cover.SUPPORT_SET_POSITION: 'Alexa.PercentageController',
# Describes a combination of devices set to a specific state, when the
# state change must occur in a specific order. For example, a "watch
# Neflix" scene might require the: 1. TV to be powered on & 2. Input set to
# HDMI1. Applies to Scenes
ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER"
# Indicates media devices with video or photo capabilities.
CAMERA = "CAMERA"
# Indicates a door.
DOOR = "DOOR"
# Indicates light sources or fixtures.
LIGHT = "LIGHT"
# An endpoint that cannot be described in on of the other categories.
OTHER = "OTHER"
# Describes a combination of devices set to a specific state, when the
# order of the state change is not important. For example a bedtime scene
# might include turning off lights and lowering the thermostat, but the
# order is unimportant. Applies to Scenes
SCENE_TRIGGER = "SCENE_TRIGGER"
# Indicates an endpoint that locks.
SMARTLOCK = "SMARTLOCK"
# Indicates modules that are plugged into an existing electrical outlet.
# Can control a variety of devices.
SMARTPLUG = "SMARTPLUG"
# Indicates the endpoint is a speaker or speaker system.
SPEAKER = "SPEAKER"
# Indicates in-wall switches wired to the electrical system. Can control a
# variety of devices.
SWITCH = "SWITCH"
# Indicates endpoints that report the temperature only.
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
# Indicates endpoints that control temperature, stand-alone air
# conditioners, or heaters with direct temperature control.
THERMOSTAT = "THERMOSTAT"
# Indicates the endpoint is a television.
# pylint: disable=invalid-name
TV = "TV"
def _capability(interface,
version=3,
supports_deactivation=None,
retrievable=None,
properties_supported=None,
cap_type='AlexaInterface'):
"""Return a Smart Home API capability object.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#capability-object
There are some additional fields allowed but not implemented here since
we've no use case for them yet:
- proactively_reported
`supports_deactivation` applies only to scenes.
"""
result = {
'type': cap_type,
'interface': interface,
'version': version,
} }
],
fan.DOMAIN: [ if supports_deactivation is not None:
'OTHER', ('Alexa.PowerController',), { result['supportsDeactivation'] = supports_deactivation
fan.SUPPORT_SET_SPEED: 'Alexa.PercentageController',
} if retrievable is not None:
], result['retrievable'] = retrievable
group.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
input_boolean.DOMAIN: ['OTHER', ('Alexa.PowerController',), None], if properties_supported is not None:
light.DOMAIN: [ result['properties'] = {'supported': properties_supported}
'LIGHT', ('Alexa.PowerController',), {
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController', return result
light.SUPPORT_RGB_COLOR: 'Alexa.ColorController',
light.SUPPORT_XY_COLOR: 'Alexa.ColorController',
light.SUPPORT_COLOR_TEMP: 'Alexa.ColorTemperatureController', class _EntityCapabilities(object):
} def __init__(self, config, entity):
], self.config = config
lock.DOMAIN: ['SMARTLOCK', ('Alexa.LockController',), None], self.entity = entity
media_player.DOMAIN: [
'TV', ('Alexa.PowerController',), { def display_categories(self):
media_player.SUPPORT_VOLUME_SET: 'Alexa.Speaker', """Return a list of display categories."""
media_player.SUPPORT_PLAY: 'Alexa.PlaybackController', entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
media_player.SUPPORT_PAUSE: 'Alexa.PlaybackController', if CONF_DISPLAY_CATEGORIES in entity_conf:
media_player.SUPPORT_STOP: 'Alexa.PlaybackController', return [entity_conf[CONF_DISPLAY_CATEGORIES]]
media_player.SUPPORT_NEXT_TRACK: 'Alexa.PlaybackController', return self.default_display_categories()
media_player.SUPPORT_PREVIOUS_TRACK: 'Alexa.PlaybackController',
} def default_display_categories(self):
], """Return a list of default display categories.
scene.DOMAIN: ['ACTIVITY_TRIGGER', ('Alexa.SceneController',), None],
script.DOMAIN: ['OTHER', ('Alexa.PowerController',), None], This can be overridden by the user in the Home Assistant configuration.
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
See also _DisplayCategory.
"""
raise NotImplementedError
def capabilities(self):
"""Return a list of supported capabilities.
If the returned list is empty, the entity will not be discovered.
You might find _capability() useful.
"""
raise NotImplementedError
class _GenericCapabilities(_EntityCapabilities):
"""A generic, on/off device.
The choice of last resort.
"""
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
return [_capability('Alexa.PowerController')]
class _SwitchCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.SWITCH]
def capabilities(self):
return [_capability('Alexa.PowerController')]
class _CoverCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.DOOR]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
class _LightCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.LIGHT]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS:
capabilities.append(_capability('Alexa.BrightnessController'))
if supported & light.SUPPORT_RGB_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
if supported & light.SUPPORT_XY_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
if supported & light.SUPPORT_COLOR_TEMP:
capabilities.append(
_capability('Alexa.ColorTemperatureController'))
return capabilities
class _FanCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
class _LockCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.SMARTLOCK]
def capabilities(self):
return [_capability('Alexa.LockController')]
class _MediaPlayerCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.TV]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.SUPPORT_VOLUME_SET:
capabilities.append(_capability('Alexa.Speaker'))
playback_features = (media_player.SUPPORT_PLAY |
media_player.SUPPORT_PAUSE |
media_player.SUPPORT_STOP |
media_player.SUPPORT_NEXT_TRACK |
media_player.SUPPORT_PREVIOUS_TRACK)
if supported & playback_features:
capabilities.append(_capability('Alexa.PlaybackController'))
return capabilities
class _SceneCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self):
return [_capability('Alexa.SceneController')]
class _ScriptCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.ACTIVITY_TRIGGER]
def capabilities(self):
can_cancel = bool(self.entity.attributes.get('can_cancel'))
return [_capability('Alexa.SceneController',
supports_deactivation=can_cancel)]
class _GroupCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self):
return [_capability('Alexa.SceneController',
supports_deactivation=True)]
class _SensorCapabilities(_EntityCapabilities):
def default_display_categories(self):
# although there are other kinds of sensors, all but temperature
# sensors are currently ignored.
return [_DisplayCategory.TEMPERATURE_SENSOR]
def capabilities(self):
capabilities = []
attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
capabilities.append(_capability(
'Alexa.TemperatureSensor',
retrievable=True,
properties_supported=[{'name': 'temperature'}]))
return capabilities
class _UnknownEntityDomainError(Exception):
pass
def _capabilities_for_entity(config, entity):
"""Return an _EntityCapabilities appropriate for given entity.
raises _UnknownEntityDomainError if the given domain is unsupported.
"""
if entity.domain not in _CAPABILITIES_FOR_DOMAIN:
raise _UnknownEntityDomainError()
return _CAPABILITIES_FOR_DOMAIN[entity.domain](config, entity)
_CAPABILITIES_FOR_DOMAIN = {
alert.DOMAIN: _GenericCapabilities,
automation.DOMAIN: _GenericCapabilities,
cover.DOMAIN: _CoverCapabilities,
fan.DOMAIN: _FanCapabilities,
group.DOMAIN: _GroupCapabilities,
input_boolean.DOMAIN: _GenericCapabilities,
light.DOMAIN: _LightCapabilities,
lock.DOMAIN: _LockCapabilities,
media_player.DOMAIN: _MediaPlayerCapabilities,
scene.DOMAIN: _SceneCapabilities,
script.DOMAIN: _ScriptCapabilities,
switch.DOMAIN: _SwitchCapabilities,
sensor.DOMAIN: _SensorCapabilities,
} }
class _Cause(object):
"""Possible causes for property changes.
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object
"""
# Indicates that the event was caused by a customer interaction with an
# application. For example, a customer switches on a light, or locks a door
# using the Alexa app or an app provided by a device vendor.
APP_INTERACTION = 'APP_INTERACTION'
# Indicates that the event was caused by a physical interaction with an
# endpoint. For example manually switching on a light or manually locking a
# door lock
PHYSICAL_INTERACTION = 'PHYSICAL_INTERACTION'
# Indicates that the event was caused by the periodic poll of an appliance,
# which found a change in value. For example, you might poll a temperature
# sensor every hour, and send the updated temperature to Alexa.
PERIODIC_POLL = 'PERIODIC_POLL'
# Indicates that the event was caused by the application of a device rule.
# For example, a customer configures a rule to switch on a light if a
# motion sensor detects motion. In this case, Alexa receives an event from
# the motion sensor, and another event from the light to indicate that its
# state change was caused by the rule.
RULE_TRIGGER = 'RULE_TRIGGER'
# Indicates that the event was caused by a voice interaction with Alexa.
# For example a user speaking to their Echo device.
VOICE_INTERACTION = 'VOICE_INTERACTION'
class Config: class Config:
"""Hold the configuration for Alexa.""" """Hold the configuration for Alexa."""
@ -80,6 +379,52 @@ class Config:
self.entity_config = entity_config or {} self.entity_config = entity_config or {}
@ha.callback
def async_setup(hass, config):
"""Activate Smart Home functionality of Alexa component.
This is optional, triggered by having a `smart_home:` sub-section in the
alexa configuration.
Even if that's disabled, the functionality in this module may still be used
by the cloud component which will call async_handle_message directly.
"""
smart_home_config = Config(
should_expose=config[CONF_FILTER],
entity_config=config.get(CONF_ENTITY_CONFIG),
)
hass.http.register_view(SmartHomeView(smart_home_config))
class SmartHomeView(http.HomeAssistantView):
"""Expose Smart Home v3 payload interface via HTTP POST."""
url = SMART_HOME_HTTP_ENDPOINT
name = 'api:alexa:smart_home'
def __init__(self, smart_home_config):
"""Initialize."""
self.smart_home_config = smart_home_config
@asyncio.coroutine
def post(self, request):
"""Handle Alexa Smart Home requests.
The Smart Home API requires the endpoint to be implemented in AWS
Lambda, which will need to forward the requests to here and pass back
the response.
"""
hass = request.app['hass']
message = yield from request.json()
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
response = yield from async_handle_message(
hass, self.smart_home_config, message)
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
return b'' if response is None else self.json(response)
@asyncio.coroutine @asyncio.coroutine
def async_handle_message(hass, config, message): def async_handle_message(hass, config, message):
"""Handle incoming API messages.""" """Handle incoming API messages."""
@ -100,7 +445,11 @@ def async_handle_message(hass, config, message):
return (yield from funct_ref(hass, config, message)) return (yield from funct_ref(hass, config, message))
def api_message(request, name='Response', namespace='Alexa', payload=None): def api_message(request,
name='Response',
namespace='Alexa',
payload=None,
context=None):
"""Create a API formatted response message. """Create a API formatted response message.
Async friendly. Async friendly.
@ -128,6 +477,9 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
if API_ENDPOINT in request: if API_ENDPOINT in request:
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy() response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
if context is not None:
response[API_CONTEXT] = context
return response return response
@ -159,9 +511,9 @@ def async_api_discovery(hass, config, request):
entity.entity_id) entity.entity_id)
continue continue
class_data = MAPPING_COMPONENT.get(entity.domain) try:
entity_capabilities = _capabilities_for_entity(config, entity)
if not class_data: except _UnknownEntityDomainError:
continue continue
entity_conf = config.entity_config.get(entity.entity_id, {}) entity_conf = config.entity_config.get(entity.entity_id, {})
@ -174,40 +526,21 @@ def async_api_discovery(hass, config, request):
scene_fmt = '{} (Scene connected via Home Assistant)' scene_fmt = '{} (Scene connected via Home Assistant)'
description = scene_fmt.format(description) description = scene_fmt.format(description)
display_categories = entity_conf.get(CONF_DISPLAY_CATEGORIES,
class_data[0])
endpoint = { endpoint = {
'displayCategories': [display_categories], 'displayCategories': entity_capabilities.display_categories(),
'additionalApplianceDetails': {}, 'additionalApplianceDetails': {},
'endpointId': entity.entity_id.replace('.', '#'), 'endpointId': entity.entity_id.replace('.', '#'),
'friendlyName': friendly_name, 'friendlyName': friendly_name,
'description': description, 'description': description,
'manufacturerName': 'Home Assistant', 'manufacturerName': 'Home Assistant',
} }
actions = set()
# static actions alexa_capabilities = entity_capabilities.capabilities()
if class_data[1]: if not alexa_capabilities:
actions |= set(class_data[1]) _LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
# dynamic actions continue
if class_data[2]: endpoint['capabilities'] = alexa_capabilities
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
for feature, action_name in class_data[2].items():
if feature & supported > 0:
actions.add(action_name)
# Write action into capabilities
capabilities = []
for action in actions:
capabilities.append({
'type': 'AlexaInterface',
'interface': action,
'version': 3,
})
endpoint['capabilities'] = capabilities
discovery_endpoints.append(endpoint) discovery_endpoints.append(endpoint)
return api_message( return api_message(
@ -216,7 +549,7 @@ def async_api_discovery(hass, config, request):
def extract_entity(funct): def extract_entity(funct):
"""Decorator for extract entity object from request.""" """Decorate for extract entity object from request."""
@asyncio.coroutine @asyncio.coroutine
def async_api_entity_wrapper(hass, config, request): def async_api_entity_wrapper(hass, config, request):
"""Process a turn on request.""" """Process a turn on request."""
@ -240,8 +573,6 @@ def extract_entity(funct):
def async_api_turn_on(hass, config, request, entity): def async_api_turn_on(hass, config, request, entity):
"""Process a turn on request.""" """Process a turn on request."""
domain = entity.domain domain = entity.domain
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
if entity.domain == cover.DOMAIN: if entity.domain == cover.DOMAIN:
@ -379,7 +710,7 @@ def async_api_decrease_color_temp(hass, config, request, entity):
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_increase_color_temp(hass, config, request, entity): def async_api_increase_color_temp(hass, config, request, entity):
"""Process a increase color temperature request.""" """Process an increase color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS)) min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
@ -396,12 +727,54 @@ def async_api_increase_color_temp(hass, config, request, entity):
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_activate(hass, config, request, entity): def async_api_activate(hass, config, request, entity):
"""Process a activate request.""" """Process an activate request."""
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, { if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False)
return api_message(request) payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
}
return api_message(
request,
name='ActivationStarted',
namespace='Alexa.SceneController',
payload=payload,
)
@HANDLERS.register(('Alexa.SceneController', 'Deactivate'))
@extract_entity
@asyncio.coroutine
def async_api_deactivate(hass, config, request, entity):
"""Process a deactivate request."""
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
payload = {
'cause': {'type': _Cause.VOICE_INTERACTION},
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
}
return api_message(
request,
name='DeactivationStarted',
namespace='Alexa.SceneController',
payload=payload,
)
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage')) @HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
@ -653,3 +1026,25 @@ def async_api_previous(hass, config, request, entity):
data, blocking=False) data, blocking=False)
return api_message(request) return api_message(request)
@HANDLERS.register(('Alexa', 'ReportState'))
@extract_entity
@asyncio.coroutine
def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
temp_property = {
'namespace': 'Alexa.TemperatureSensor',
'name': 'temperature',
'value': {
'value': float(entity.state),
'scale': API_TEMP_UNITS[unit],
},
}
return api_message(
request,
name='StateReport',
context={'properties': [temp_property]}
)

View File

@ -7,13 +7,14 @@ https://home-assistant.io/components/apple_tv/
import asyncio import asyncio
import logging import logging
from typing import Sequence, TypeVar, Union
import voluptuous as vol import voluptuous as vol
from typing import Union, TypeVar, Sequence
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV from homeassistant.components.discovery import SERVICE_APPLE_TV
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.9'] REQUIREMENTS = ['pyatv==0.3.9']
@ -59,9 +60,9 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(ensure_list, [vol.Schema({ DOMAIN: vol.All(ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string, vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string, vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean,
})]) })])
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -140,7 +141,7 @@ def async_setup(hass, config):
@asyncio.coroutine @asyncio.coroutine
def async_service_handler(service): def async_service_handler(service):
"""Handler for service calls.""" """Handle service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID) entity_ids = service.data.get(ATTR_ENTITY_ID)
if service.service == SERVICE_SCAN: if service.service == SERVICE_SCAN:
@ -167,7 +168,7 @@ def async_setup(hass, config):
@asyncio.coroutine @asyncio.coroutine
def atv_discovered(service, info): def atv_discovered(service, info):
"""Setup an Apple TV that was auto discovered.""" """Set up an Apple TV that was auto discovered."""
yield from _setup_atv(hass, { yield from _setup_atv(hass, {
CONF_NAME: info['name'], CONF_NAME: info['name'],
CONF_HOST: info['host'], CONF_HOST: info['host'],
@ -194,7 +195,7 @@ def async_setup(hass, config):
@asyncio.coroutine @asyncio.coroutine
def _setup_atv(hass, atv_config): def _setup_atv(hass, atv_config):
"""Setup an Apple TV.""" """Set up an Apple TV."""
import pyatv import pyatv
name = atv_config.get(CONF_NAME) name = atv_config.get(CONF_NAME)
host = atv_config.get(CONF_HOST) host = atv_config.get(CONF_HOST)
@ -245,7 +246,7 @@ class AppleTVPowerManager:
@property @property
def turned_on(self): def turned_on(self):
"""If device is on or off.""" """Return true if device is on or off."""
return self._is_on return self._is_on
def set_power_on(self, value): def set_power_on(self, value):

View File

@ -1,34 +1,34 @@
"""Support for Asterisk Voicemail interface.""" """
Support for Asterisk Voicemail interface.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/asterisk_mbox/
"""
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import (CONF_HOST,
CONF_PORT, CONF_PASSWORD)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (async_dispatcher_connect, from homeassistant.helpers import discovery
async_dispatcher_send) import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
REQUIREMENTS = ['asterisk_mbox==0.4.0'] REQUIREMENTS = ['asterisk_mbox==0.4.0']
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' _LOGGER = logging.getLogger(__name__)
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
DOMAIN = 'asterisk_mbox' DOMAIN = 'asterisk_mbox'
_LOGGER = logging.getLogger(__name__) SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): int,
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_PORT): int,
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -43,7 +43,7 @@ def setup(hass, config):
hass.data[DOMAIN] = AsteriskData(hass, host, port, password) hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config) discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
return True return True
@ -68,15 +68,14 @@ class AsteriskData(object):
from asterisk_mbox.commands import CMD_MESSAGE_LIST from asterisk_mbox.commands import CMD_MESSAGE_LIST
if command == CMD_MESSAGE_LIST: if command == CMD_MESSAGE_LIST:
_LOGGER.info("AsteriskVM sent updated message list") _LOGGER.debug("AsteriskVM sent updated message list")
self.messages = sorted(msg, self.messages = sorted(
key=lambda item: item['info']['origtime'], msg, key=lambda item: item['info']['origtime'], reverse=True)
reverse=True) async_dispatcher_send(
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
self.messages)
@callback @callback
def _request_messages(self): def _request_messages(self):
"""Handle changes to the mailbox.""" """Handle changes to the mailbox."""
_LOGGER.info("Requesting message list") _LOGGER.debug("Requesting message list")
self.client.messages() self.client.messages()

View File

@ -338,10 +338,9 @@ class AutomationEntity(ToggleEntity):
yield from self.async_update_ha_state() yield from self.async_update_ha_state()
@asyncio.coroutine @asyncio.coroutine
def async_remove(self): def async_will_remove_from_hass(self):
"""Remove automation from HASS.""" """Remove listeners when removing automation from HASS."""
yield from self.async_turn_off() yield from self.async_turn_off()
yield from super().async_remove()
@asyncio.coroutine @asyncio.coroutine
def async_enable(self): def async_enable(self):

View File

@ -4,16 +4,14 @@ Support for Axis devices.
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/axis/ https://home-assistant.io/components/axis/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.discovery import SERVICE_AXIS from homeassistant.components.discovery import SERVICE_AXIS
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED, from homeassistant.const import (
CONF_EVENT, CONF_HOST, CONF_INCLUDE, ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE,
CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME,
CONF_TRIGGER_TIME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
@ -21,7 +19,6 @@ from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['axis==14'] REQUIREMENTS = ['axis==14']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -81,10 +78,10 @@ def request_configuration(hass, config, name, host, serialnumber):
configurator = hass.components.configurator configurator = hass.components.configurator
def configuration_callback(callback_data): def configuration_callback(callback_data):
"""Called when config is submitted.""" """Call when configuration is submitted."""
if CONF_INCLUDE not in callback_data: if CONF_INCLUDE not in callback_data:
configurator.notify_errors(request_id, configurator.notify_errors(
"Functionality mandatory.") request_id, "Functionality mandatory.")
return False return False
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split() callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
@ -96,18 +93,20 @@ def request_configuration(hass, config, name, host, serialnumber):
try: try:
device_config = DEVICE_SCHEMA(callback_data) device_config = DEVICE_SCHEMA(callback_data)
except vol.Invalid: except vol.Invalid:
configurator.notify_errors(request_id, configurator.notify_errors(
"Bad input, please check spelling.") request_id, "Bad input, please check spelling.")
return False return False
if setup_device(hass, config, device_config): if setup_device(hass, config, device_config):
del device_config['events']
del device_config['signal']
config_file = load_json(hass.config.path(CONFIG_FILE)) config_file = load_json(hass.config.path(CONFIG_FILE))
config_file[serialnumber] = dict(device_config) config_file[serialnumber] = dict(device_config)
save_json(hass.config.path(CONFIG_FILE), config_file) save_json(hass.config.path(CONFIG_FILE), config_file)
configurator.request_done(request_id) configurator.request_done(request_id)
else: else:
configurator.notify_errors(request_id, configurator.notify_errors(
"Failed to register, please try again.") request_id, "Failed to register, please try again.")
return False return False
title = '{} ({})'.format(name, host) title = '{} ({})'.format(name, host)
@ -145,7 +144,7 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config): def setup(hass, config):
"""Common setup for Axis devices.""" """Set up for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument def _shutdown(call): # pylint: disable=unused-argument
"""Stop the event stream on shutdown.""" """Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items(): for serialnumber, device in AXIS_DEVICES.items():
@ -155,7 +154,7 @@ def setup(hass, config):
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
def axis_device_discovered(service, discovery_info): def axis_device_discovered(service, discovery_info):
"""Called when axis devices has been found.""" """Call when axis devices has been found."""
host = discovery_info[CONF_HOST] host = discovery_info[CONF_HOST]
name = discovery_info['hostname'] name = discovery_info['hostname']
serialnumber = discovery_info['properties']['macaddress'] serialnumber = discovery_info['properties']['macaddress']
@ -171,8 +170,8 @@ def setup(hass, config):
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err) _LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
return False return False
if not setup_device(hass, config, device_config): if not setup_device(hass, config, device_config):
_LOGGER.error("Couldn\'t set up %s", _LOGGER.error(
device_config[CONF_NAME]) "Couldn't set up %s", device_config[CONF_NAME])
else: else:
# New device, create configuration request for UI # New device, create configuration request for UI
request_configuration(hass, config, name, host, serialnumber) request_configuration(hass, config, name, host, serialnumber)
@ -191,7 +190,7 @@ def setup(hass, config):
if CONF_NAME not in device_config: if CONF_NAME not in device_config:
device_config[CONF_NAME] = device device_config[CONF_NAME] = device
if not setup_device(hass, config, device_config): if not setup_device(hass, config, device_config):
_LOGGER.error("Couldn\'t set up %s", device_config[CONF_NAME]) _LOGGER.error("Couldn't set up %s", device_config[CONF_NAME])
def vapix_service(call): def vapix_service(call):
"""Service to send a message.""" """Service to send a message."""
@ -203,23 +202,21 @@ def setup(hass, config):
call.data[SERVICE_PARAM]) call.data[SERVICE_PARAM])
hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response) hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response)
return True return True
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME]) _LOGGER.info("Couldn't find device %s", call.data[CONF_NAME])
return False return False
# Register service with Home Assistant. # Register service with Home Assistant.
hass.services.register(DOMAIN, hass.services.register(
SERVICE_VAPIX_CALL, DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA)
vapix_service,
schema=SERVICE_SCHEMA)
return True return True
def setup_device(hass, config, device_config): def setup_device(hass, config, device_config):
"""Set up device.""" """Set up an Axis device."""
from axis import AxisDevice from axis import AxisDevice
def signal_callback(action, event): def signal_callback(action, event):
"""Callback to configure events when initialized on event stream.""" """Call to configure events when initialized on event stream."""
if action == 'add': if action == 'add':
event_config = { event_config = {
CONF_EVENT: event, CONF_EVENT: event,
@ -228,11 +225,8 @@ def setup_device(hass, config, device_config):
CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME] CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME]
} }
component = event.event_platform component = event.event_platform
discovery.load_platform(hass, discovery.load_platform(
component, hass, component, DOMAIN, event_config, config)
DOMAIN,
event_config,
config)
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE],
EVENT_TYPES)) EVENT_TYPES))
@ -243,7 +237,7 @@ def setup_device(hass, config, device_config):
if device.serial_number is None: if device.serial_number is None:
# If there is no serial number a connection could not be made # If there is no serial number a connection could not be made
_LOGGER.error("Couldn\'t connect to %s", device_config[CONF_HOST]) _LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST])
return False return False
for component in device_config[CONF_INCLUDE]: for component in device_config[CONF_INCLUDE]:
@ -255,11 +249,8 @@ def setup_device(hass, config, device_config):
CONF_USERNAME: device_config[CONF_USERNAME], CONF_USERNAME: device_config[CONF_USERNAME],
CONF_PASSWORD: device_config[CONF_PASSWORD] CONF_PASSWORD: device_config[CONF_PASSWORD]
} }
discovery.load_platform(hass, discovery.load_platform(
component, hass, component, DOMAIN, camera_config, config)
DOMAIN,
camera_config,
config)
AXIS_DEVICES[device.serial_number] = device AXIS_DEVICES[device.serial_number] = device
if event_types: if event_types:
@ -273,8 +264,8 @@ class AxisDeviceEvent(Entity):
def __init__(self, event_config): def __init__(self, event_config):
"""Initialize the event.""" """Initialize the event."""
self.axis_event = event_config[CONF_EVENT] self.axis_event = event_config[CONF_EVENT]
self._name = '{}_{}_{}'.format(event_config[CONF_NAME], self._name = '{}_{}_{}'.format(
self.axis_event.event_type, event_config[CONF_NAME], self.axis_event.event_type,
self.axis_event.id) self.axis_event.id)
self.location = event_config[ATTR_LOCATION] self.location = event_config[ATTR_LOCATION]
self.axis_event.callback = self._update_callback self.axis_event.callback = self._update_callback
@ -296,7 +287,7 @@ class AxisDeviceEvent(Entity):
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed.""" """Return the polling state. No polling needed."""
return False return False
@property @property

View File

@ -3,23 +3,22 @@ Support for ADS binary sensors.
For more details about this platform, please refer to the documentation. For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/binary_sensor.ads/ https://home-assistant.io/components/binary_sensor.ads/
""" """
import asyncio import asyncio
import logging import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDevice, \
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS binary sensor' DEFAULT_NAME = 'ADS binary sensor'
DEPENDENCIES = ['ads']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string, vol.Required(CONF_ADS_VAR): cv.string,
@ -44,7 +43,7 @@ class AdsBinarySensor(BinarySensorDevice):
"""Representation of ADS binary sensors.""" """Representation of ADS binary sensors."""
def __init__(self, ads_hub, name, ads_var, device_class): def __init__(self, ads_hub, name, ads_var, device_class):
"""Initialize AdsBinarySensor entity.""" """Initialize ADS binary sensor."""
self._name = name self._name = name
self._state = False self._state = False
self._device_class = device_class or 'moving' self._device_class = device_class or 'moving'
@ -56,15 +55,13 @@ class AdsBinarySensor(BinarySensorDevice):
"""Register device notification.""" """Register device notification."""
def update(name, value): def update(name, value):
"""Handle device notifications.""" """Handle device notifications."""
_LOGGER.debug('Variable %s changed its value to %d', _LOGGER.debug('Variable %s changed its value to %d', name, value)
name, value)
self._state = value self._state = value
self.schedule_update_ha_state() self.schedule_update_ha_state()
self.hass.async_add_job( self.hass.async_add_job(
self._ads_hub.add_device_notification, self._ads_hub.add_device_notification,
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update self.ads_var, self._ads_hub.PLCTYPE_BOOL, update)
)
@property @property
def name(self): def name(self):

View File

@ -4,13 +4,12 @@ Support for Axis binary sensors.
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.axis/ https://home-assistant.io/components/binary_sensor.axis/
""" """
import logging
from datetime import timedelta from datetime import timedelta
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice) from homeassistant.components.axis import AxisDeviceEvent
from homeassistant.components.axis import (AxisDeviceEvent) from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (CONF_TRIGGER_TIME) from homeassistant.const import CONF_TRIGGER_TIME
from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -20,7 +19,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):
"""Setup Axis device event.""" """Set up the Axis binary devices."""
add_devices([AxisBinarySensor(hass, discovery_info)], True) add_devices([AxisBinarySensor(hass, discovery_info)], True)
@ -28,7 +27,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
"""Representation of a binary Axis event.""" """Representation of a binary Axis event."""
def __init__(self, hass, event_config): def __init__(self, hass, event_config):
"""Initialize the binary sensor.""" """Initialize the Axis binary sensor."""
self.hass = hass self.hass = hass
self._state = False self._state = False
self._delay = event_config[CONF_TRIGGER_TIME] self._delay = event_config[CONF_TRIGGER_TIME]
@ -56,7 +55,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
# Set timer to wait until updating the state # Set timer to wait until updating the state
def _delay_update(now): def _delay_update(now):
"""Timer callback for sensor update.""" """Timer callback for sensor update."""
_LOGGER.debug("%s Called delayed (%s sec) update.", _LOGGER.debug("%s called delayed (%s sec) update",
self._name, self._delay) self._name, self._delay)
self.schedule_update_ha_state() self.schedule_update_ha_state()
self._timer = None self._timer = None

View File

@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors = [] sensors = []
try: try:
_LOGGER.debug("Initializing Client") _LOGGER.debug("Initializing client")
client = concord232_client.Client('http://{}:{}'.format(host, port)) client = concord232_client.Client('http://{}:{}'.format(host, port))
client.zones = client.list_zones() client.zones = client.list_zones()
client.last_zone_update = datetime.datetime.now() client.last_zone_update = datetime.datetime.now()

View File

@ -4,7 +4,6 @@ Support for deCONZ binary sensor.
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/binary_sensor.deconz/ https://home-assistant.io/components/binary_sensor.deconz/
""" """
import asyncio import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -17,7 +16,7 @@ DEPENDENCIES = ['deconz']
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup binary sensor for deCONZ component.""" """Set up the deCONZ binary sensor."""
if discovery_info is None: if discovery_info is None:
return return
@ -25,8 +24,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
sensors = hass.data[DECONZ_DATA].sensors sensors = hass.data[DECONZ_DATA].sensors
entities = [] entities = []
for sensor in sensors.values(): for key in sorted(sensors.keys(), key=int):
if sensor.type in DECONZ_BINARY_SENSOR: sensor = sensors[key]
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
entities.append(DeconzBinarySensor(sensor)) entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True) async_add_devices(entities, True)
@ -35,7 +35,7 @@ class DeconzBinarySensor(BinarySensorDevice):
"""Representation of a binary sensor.""" """Representation of a binary sensor."""
def __init__(self, sensor): def __init__(self, sensor):
"""Setup sensor and add update callback to get data from websocket.""" """Set up sensor and add update callback to get data from websocket."""
self._sensor = sensor self._sensor = sensor
@asyncio.coroutine @asyncio.coroutine
@ -67,7 +67,7 @@ class DeconzBinarySensor(BinarySensorDevice):
@property @property
def device_class(self): def device_class(self):
"""Class of the sensor.""" """Return the class of the sensor."""
return self._sensor.sensor_class return self._sensor.sensor_class
@property @property

View File

@ -238,6 +238,5 @@ class FlicButton(BinarySensorDevice):
import pyflic import pyflic
if connection_status == pyflic.ConnectionStatus.Disconnected: if connection_status == pyflic.ConnectionStatus.Disconnected:
_LOGGER.info("Button (%s) disconnected. Reason: %s", _LOGGER.warning("Button (%s) disconnected. Reason: %s",
self.address, disconnect_reason) self.address, disconnect_reason)
self.remove()

View File

@ -0,0 +1,95 @@
"""IHC binary sensor platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
from xml.etree.ElementTree import Element
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.components.ihc import (
validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import CONF_INVERTING
from homeassistant.components.ihc.ihcdevice import IHCDevice
from homeassistant.const import (
CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['ihc']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BINARY_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=None): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name)
])
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the IHC binary sensor platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
devices = []
if discovery_info:
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg[CONF_TYPE],
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
else:
binary_sensors = config[CONF_BINARY_SENSORS]
for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg[CONF_TYPE]
inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting)
devices.append(sensor)
add_devices(devices)
class IHCBinarySensor(IHCDevice, BinarySensorDevice):
"""IHC Binary Sensor.
The associated IHC resource can be any in or output from a IHC product
or function block, but it must be a boolean ON/OFF resources.
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool, product: Element=None):
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None
self._sensor_type = sensor_type
self.inverting = inverting
@property
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type
@property
def is_on(self):
"""Return true if the binary sensor is on/open."""
return self._state
def on_ihc_change(self, ihc_id, value):
"""IHC resource has changed."""
if self.inverting:
self._state = not value
else:
self._state = value
self.schedule_update_ha_state()

View File

@ -5,12 +5,13 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/ https://home-assistant.io/components/binary_sensor.knx/
""" """
import asyncio import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \ from homeassistant.components.binary_sensor import (
KNXAutomation PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \ from homeassistant.components.knx import (
BinarySensorDevice ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation)
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -53,20 +54,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
discovery_info=None):
"""Set up binary sensor(s) for KNX platform.""" """Set up binary sensor(s) for KNX platform."""
if DATA_KNX not in hass.data \ if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
or not hass.data[DATA_KNX].initialized: return
return False
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:
async_add_devices_config(hass, config, async_add_devices) async_add_devices_config(hass, config, async_add_devices)
return True
@callback @callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices): def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@ -80,7 +77,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback @callback
def async_add_devices_config(hass, config, async_add_devices): def async_add_devices_config(hass, config, async_add_devices):
"""Set up binary senor for KNX platform configured within plattform.""" """Set up binary senor for KNX platform configured within platform."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
import xknx import xknx
binary_sensor = xknx.devices.BinarySensor( binary_sensor = xknx.devices.BinarySensor(
@ -108,7 +105,7 @@ class KNXBinarySensor(BinarySensorDevice):
"""Representation of a KNX binary sensor.""" """Representation of a KNX binary sensor."""
def __init__(self, hass, device): def __init__(self, hass, device):
"""Initialization of KNXBinarySensor.""" """Initialize of KNX binary sensor."""
self.device = device self.device = device
self.hass = hass self.hass = hass
self.async_register_callbacks() self.async_register_callbacks()
@ -119,7 +116,7 @@ class KNXBinarySensor(BinarySensorDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
@asyncio.coroutine @asyncio.coroutine
def after_update_callback(device): def after_update_callback(device):
"""Callback after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument # pylint: disable=unused-argument
yield from self.async_update_ha_state() yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)

View File

@ -36,7 +36,7 @@ class MaxCubeShutter(BinarySensorDevice):
def __init__(self, hass, name, rf_address): def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube BinarySensorDevice.""" """Initialize MAX! Cube BinarySensorDevice."""
self._name = name self._name = name
self._sensor_type = 'opening' self._sensor_type = 'window'
self._rf_address = rf_address self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE] self._cubehandle = hass.data[MAXCUBE_HANDLE]
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN

View File

@ -0,0 +1,85 @@
"""Support for MyChevy sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mychevy/
"""
import asyncio
import logging
from homeassistant.components.mychevy import (
EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC
)
from homeassistant.components.binary_sensor import (
ENTITY_ID_FORMAT, BinarySensorDevice)
from homeassistant.core import callback
from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
SENSORS = [
EVBinarySensorConfig("Plugged In", "plugged_in", "plug")
]
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MyChevy sensors."""
if discovery_info is None:
return
sensors = []
hub = hass.data[MYCHEVY_DOMAIN]
for sconfig in SENSORS:
sensors.append(EVBinarySensor(hub, sconfig))
async_add_devices(sensors)
class EVBinarySensor(BinarySensorDevice):
"""Base EVSensor class.
The only real difference between sensors is which units and what
attribute from the car object they are returning. All logic can be
built with just setting subclass attributes.
"""
def __init__(self, connection, config):
"""Initialize sensor with car connection."""
self._conn = connection
self._name = config.name
self._attr = config.attr
self._type = config.device_class
self._is_on = None
self.entity_id = ENTITY_ID_FORMAT.format(
'{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name)))
@property
def name(self):
"""Return the name."""
return self._name
@property
def is_on(self):
"""Return if on."""
return self._is_on
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback)
@callback
def async_update_callback(self):
"""Update state."""
if self._conn.car is not None:
self._is_on = getattr(self._conn.car, self._attr, None)
self.async_schedule_update_ha_state()
@property
def should_poll(self):
"""Return the polling state."""
return False

View File

@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mysensors/ https://home-assistant.io/components/binary_sensor.mysensors/
""" """
from homeassistant.components import mysensors from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN, from homeassistant.components.binary_sensor import (
BinarySensorDevice) DEVICE_CLASSES, DOMAIN, BinarySensorDevice)
from homeassistant.const import STATE_ON from homeassistant.const import STATE_ON
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for binary sensors.""" """Set up the MySensors platform for binary sensors."""
mysensors.setup_mysensors_platform( mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsBinarySensor, hass, DOMAIN, discovery_info, MySensorsBinarySensor,
add_devices=add_devices) add_devices=add_devices)
class MySensorsBinarySensor( class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
mysensors.MySensorsEntity, BinarySensorDevice): """Representation of a MySensors Binary Sensor child node."""
"""Represent the value of a MySensors Binary Sensor child node."""
@property @property
def is_on(self): def is_on(self):

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mystrom/
import asyncio import asyncio
import logging import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice, DOMAIN) from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY
@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView):
@asyncio.coroutine @asyncio.coroutine
def get(self, request): def get(self, request):
"""The GET request received from a myStrom button.""" """Handle the GET request received from a myStrom button."""
res = yield from self._handle(request.app['hass'], request.query) res = yield from self._handle(request.app['hass'], request.query)
return res return res

View File

@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.raspihats/ https://home-assistant.io/components/binary_sensor.raspihats/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_DEVICE_CLASS, DEVICE_DEFAULT_NAME
)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice PLATFORM_SCHEMA, BinarySensorDevice)
)
from homeassistant.components.raspihats import ( from homeassistant.components.raspihats import (
CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX, CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX,
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException)
) from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the raspihats binary_sensor devices.""" """Set up the raspihats binary_sensor devices."""
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER] I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
binary_sensors = [] binary_sensors = []
i2c_hat_configs = config.get(CONF_I2C_HATS) i2c_hat_configs = config.get(CONF_I2C_HATS)
@ -65,39 +64,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
) )
) )
except I2CHatsException as ex: except I2CHatsException as ex:
_LOGGER.error( _LOGGER.error("Failed to register %s I2CHat@%s %s",
"Failed to register " + board + "I2CHat@" + hex(address) + " " board, hex(address), str(ex))
+ str(ex)
)
add_devices(binary_sensors) add_devices(binary_sensors)
class I2CHatBinarySensor(BinarySensorDevice): class I2CHatBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that uses a I2C-HAT digital input.""" """Representation of a binary sensor that uses a I2C-HAT digital input."""
I2C_HATS_MANAGER = None I2C_HATS_MANAGER = None
def __init__(self, address, channel, name, invert_logic, device_class): def __init__(self, address, channel, name, invert_logic, device_class):
"""Initialize sensor.""" """Initialize the raspihats sensor."""
self._address = address self._address = address
self._channel = channel self._channel = channel
self._name = name or DEVICE_DEFAULT_NAME self._name = name or DEVICE_DEFAULT_NAME
self._invert_logic = invert_logic self._invert_logic = invert_logic
self._device_class = device_class self._device_class = device_class
self._state = self.I2C_HATS_MANAGER.read_di( self._state = self.I2C_HATS_MANAGER.read_di(
self._address, self._address, self._channel)
self._channel
)
def online_callback(): def online_callback():
"""Callback fired when board is online.""" """Call fired when board is online."""
self.schedule_update_ha_state() self.schedule_update_ha_state()
self.I2C_HATS_MANAGER.register_online_callback( self.I2C_HATS_MANAGER.register_online_callback(
self._address, self._address, self._channel, online_callback)
self._channel,
online_callback
)
def edge_callback(state): def edge_callback(state):
"""Read digital input state.""" """Read digital input state."""
@ -105,10 +97,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
self.schedule_update_ha_state() self.schedule_update_ha_state()
self.I2C_HATS_MANAGER.register_di_callback( self.I2C_HATS_MANAGER.register_di_callback(
self._address, self._address, self._channel, edge_callback)
self._channel,
edge_callback
)
@property @property
def device_class(self): def device_class(self):
@ -122,7 +111,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
@property @property
def should_poll(self): def should_poll(self):
"""Polling not needed for this sensor.""" """No polling needed for this sensor."""
return False return False
@property @property

View File

@ -1,38 +1,36 @@
""" """
Support for RFXtrx binary sensors. Support for RFXtrx binary sensors.
Lighting4 devices (sensors based on PT2262 encoder) are supported and For more details about this platform, please refer to the documentation at
tested. Other types may need some work. https://home-assistant.io/components/binary_sensor.rfxtrx/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF, CONF_NAME)
from homeassistant.components import rfxtrx from homeassistant.components import rfxtrx
from homeassistant.helpers import event as evt
from homeassistant.helpers import config_validation as cv
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA) PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
from homeassistant.components.rfxtrx import ( from homeassistant.components.rfxtrx import (
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
CONF_OFF_DELAY, CONF_DATA_BITS, CONF_DEVICES) CONF_FIRE_EVENT, CONF_OFF_DELAY)
from homeassistant.util import slugify from homeassistant.const import (
CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import event as evt
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util import slugify
DEPENDENCIES = ["rfxtrx"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['rfxtrx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): { vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({ cv.string: vol.Schema({
vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Optional(CONF_DEVICE_CLASS, default=None):
DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_OFF_DELAY, default=None): vol.Optional(CONF_OFF_DELAY, default=None):
vol.Any(cv.time_period, cv.positive_timedelta), vol.Any(cv.time_period, cv.positive_timedelta),
@ -45,8 +43,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Binary Sensor platform to rfxtrx.""" """Set up the Binary Sensor platform to RFXtrx."""
import RFXtrx as rfxtrxmod import RFXtrx as rfxtrxmod
sensors = [] sensors = []
@ -58,29 +56,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
continue continue
if entity[CONF_DATA_BITS] is not None: if entity[CONF_DATA_BITS] is not None:
_LOGGER.debug("Masked device id: %s", _LOGGER.debug(
rfxtrx.get_pt2262_deviceid(device_id, "Masked device id: %s", rfxtrx.get_pt2262_deviceid(
entity[CONF_DATA_BITS])) device_id, entity[CONF_DATA_BITS]))
_LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)", _LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)",
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor(event, entity[ATTR_NAME], device = RfxtrxBinarySensor(
entity[CONF_DEVICE_CLASS], event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS],
entity[CONF_FIRE_EVENT], entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY],
entity[CONF_OFF_DELAY], entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON],
entity[CONF_DATA_BITS],
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF]) entity[CONF_COMMAND_OFF])
device.hass = hass device.hass = hass
sensors.append(device) sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device rfxtrx.RFX_DEVICES[device_id] = device
add_devices_callback(sensors) add_devices(sensors)
# pylint: disable=too-many-branches
def binary_sensor_update(event): def binary_sensor_update(event):
"""Callback for control updates from the RFXtrx gateway.""" """Call for control updates from the RFXtrx gateway."""
if not isinstance(event, rfxtrxmod.ControlEvent): if not isinstance(event, rfxtrxmod.ControlEvent):
return return
@ -100,29 +95,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
poss_dev = rfxtrx.find_possible_pt2262_device(device_id) poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None: if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower()) poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.debug("Found possible matching deviceid %s.", _LOGGER.debug(
poss_id) "Found possible matching device ID: %s", poss_id)
pkt_id = "".join("{0:02x}".format(x) for x in event.data) pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id) sensor = RfxtrxBinarySensor(event, pkt_id)
sensor.hass = hass sensor.hass = hass
rfxtrx.RFX_DEVICES[device_id] = sensor rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor]) add_devices([sensor])
_LOGGER.info("Added binary sensor %s " _LOGGER.info(
"(Device_id: %s Class: %s Sub: %s)", "Added binary sensor %s (Device ID: %s Class: %s Sub: %s)",
pkt_id, pkt_id, slugify(event.device.id_string.lower()),
slugify(event.device.id_string.lower()), event.device.__class__.__name__, event.device.subtype)
event.device.__class__.__name__,
event.device.subtype)
elif not isinstance(sensor, RfxtrxBinarySensor): elif not isinstance(sensor, RfxtrxBinarySensor):
return return
else: else:
_LOGGER.debug("Binary sensor update " _LOGGER.debug(
"(Device_id: %s Class: %s Sub: %s)", "Binary sensor update (Device ID: %s Class: %s Sub: %s)",
slugify(event.device.id_string.lower()), slugify(event.device.id_string.lower()),
event.device.__class__.__name__, event.device.__class__.__name__, event.device.subtype)
event.device.subtype)
if sensor.is_lighting4: if sensor.is_lighting4:
if sensor.data_bits is not None: if sensor.data_bits is not None:
@ -142,22 +134,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
sensor.update_state(False) sensor.update_state(False)
sensor.delay_listener = evt.track_point_in_time( sensor.delay_listener = evt.track_point_in_time(
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay)
)
# Subscribe to main rfxtrx events # Subscribe to main RFXtrx events
if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update) rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
# pylint: disable=too-many-instance-attributes,too-many-arguments
class RfxtrxBinarySensor(BinarySensorDevice): class RfxtrxBinarySensor(BinarySensorDevice):
"""An Rfxtrx binary sensor.""" """A representation of a RFXtrx binary sensor."""
def __init__(self, event, name, device_class=None, def __init__(self, event, name, device_class=None,
should_fire=False, off_delay=None, data_bits=None, should_fire=False, off_delay=None, data_bits=None,
cmd_on=None, cmd_off=None): cmd_on=None, cmd_off=None):
"""Initialize the sensor.""" """Initialize the RFXtrx sensor."""
self.event = event self.event = event
self._name = name self._name = name
self._should_fire_event = should_fire self._should_fire_event = should_fire
@ -172,8 +162,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
if data_bits is not None: if data_bits is not None:
self._masked_id = rfxtrx.get_pt2262_deviceid( self._masked_id = rfxtrx.get_pt2262_deviceid(
event.device.id_string.lower(), event.device.id_string.lower(), data_bits)
data_bits)
else: else:
self._masked_id = None self._masked_id = None

View File

@ -8,18 +8,17 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.rpi_pfio as rpi_pfio
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA) PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.components.rpi_pfio as rpi_pfio
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_NAME = 'name' CONF_INVERT_LOGIC = 'invert_logic'
ATTR_INVERT_LOGIC = 'invert_logic'
ATTR_SETTLE_TIME = 'settle_time'
CONF_PORTS = 'ports' CONF_PORTS = 'ports'
CONF_SETTLE_TIME = 'settle_time'
DEFAULT_INVERT_LOGIC = False DEFAULT_INVERT_LOGIC = False
DEFAULT_SETTLE_TIME = 20 DEFAULT_SETTLE_TIME = 20
@ -27,27 +26,27 @@ DEFAULT_SETTLE_TIME = 20
DEPENDENCIES = ['rpi_pfio'] DEPENDENCIES = ['rpi_pfio']
PORT_SCHEMA = vol.Schema({ PORT_SCHEMA = vol.Schema({
vol.Optional(ATTR_NAME, default=None): cv.string, vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(ATTR_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
cv.positive_int, cv.positive_int,
vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
}) })
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORTS, default={}): vol.Schema({ vol.Optional(CONF_PORTS, default={}): vol.Schema({
cv.positive_int: PORT_SCHEMA cv.positive_int: PORT_SCHEMA,
}) })
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the PiFace Digital Input devices.""" """Set up the PiFace Digital Input devices."""
binary_sensors = [] binary_sensors = []
ports = config.get('ports') ports = config.get(CONF_PORTS)
for port, port_entity in ports.items(): for port, port_entity in ports.items():
name = port_entity[ATTR_NAME] name = port_entity[CONF_NAME]
settle_time = port_entity[ATTR_SETTLE_TIME] / 1000 settle_time = port_entity[CONF_SETTLE_TIME] / 1000
invert_logic = port_entity[ATTR_INVERT_LOGIC] invert_logic = port_entity[CONF_INVERT_LOGIC]
binary_sensors.append(RPiPFIOBinarySensor( binary_sensors.append(RPiPFIOBinarySensor(
hass, port, name, settle_time, invert_logic)) hass, port, name, settle_time, invert_logic))

View File

@ -4,46 +4,49 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
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.spc/ https://home-assistant.io/components/binary_sensor.spc/
""" """
import logging
import asyncio import asyncio
import logging
from homeassistant.components.spc import (
ATTR_DISCOVER_DEVICES, DATA_REGISTRY)
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import (STATE_UNAVAILABLE, STATE_ON, STATE_OFF) from homeassistant.components.spc import ATTR_DISCOVER_DEVICES, DATA_REGISTRY
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion', SPC_TYPE_TO_DEVICE_CLASS = {
'0': 'motion',
'1': 'opening', '1': 'opening',
'3': 'smoke'} '3': 'smoke',
}
SPC_INPUT_TO_SENSOR_STATE = {
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF, '0': STATE_OFF,
'1': STATE_ON} '1': STATE_ON,
}
def _get_device_class(spc_type): def _get_device_class(spc_type):
"""Get the device class."""
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None) return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
def _get_sensor_state(spc_input): def _get_sensor_state(spc_input):
"""Get the sensor state."""
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE) return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
def _create_sensor(hass, zone): def _create_sensor(hass, zone):
return SpcBinarySensor(zone_id=zone['id'], """Create a SPC sensor."""
name=zone['zone_name'], return SpcBinarySensor(
zone_id=zone['id'], name=zone['zone_name'],
state=_get_sensor_state(zone['input']), state=_get_sensor_state(zone['input']),
device_class=_get_device_class(zone['type']), device_class=_get_device_class(zone['type']),
spc_registry=hass.data[DATA_REGISTRY]) spc_registry=hass.data[DATA_REGISTRY])
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
discovery_info=None): """Set up the SPC binary sensor."""
"""Initialize the platform."""
if (discovery_info is None or if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None): discovery_info[ATTR_DISCOVER_DEVICES] is None):
return return
@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices,
class SpcBinarySensor(BinarySensorDevice): class SpcBinarySensor(BinarySensorDevice):
"""Represents a sensor based on an SPC zone.""" """Representation of a sensor based on a SPC zone."""
def __init__(self, zone_id, name, state, device_class, spc_registry): def __init__(self, zone_id, name, state, device_class, spc_registry):
"""Initialize the sensor device.""" """Initialize the sensor device."""
@ -74,7 +77,7 @@ class SpcBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""The name of the device.""" """Return the name of the device."""
return self._name return self._name
@property @property
@ -85,7 +88,7 @@ class SpcBinarySensor(BinarySensorDevice):
@property @property
def hidden(self) -> bool: def hidden(self) -> bool:
"""Whether the device is hidden by default.""" """Whether the device is hidden by default."""
# these type of sensors are probably mainly used for automations # These type of sensors are probably mainly used for automations
return True return True
@property @property
@ -95,5 +98,5 @@ class SpcBinarySensor(BinarySensorDevice):
@property @property
def device_class(self): def device_class(self):
"""The device class.""" """Return the device class."""
return self._device_class return self._device_class

View File

@ -4,15 +4,15 @@ Support for Taps Affs.
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.tapsaff/ https://home-assistant.io/components/binary_sensor.tapsaff/
""" """
import logging
from datetime import timedelta from datetime import timedelta
import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA) PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import (CONF_NAME) from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['tapsaff==0.1.3'] REQUIREMENTS = ['tapsaff==0.1.3']
@ -67,7 +67,7 @@ class TapsAffData(object):
"""Class for handling the data retrieval for pins.""" """Class for handling the data retrieval for pins."""
def __init__(self, location): def __init__(self, location):
"""Initialize the sensor.""" """Initialize the data object."""
from tapsaff import TapsAff from tapsaff import TapsAff
self._is_taps_aff = None self._is_taps_aff = None

View File

@ -28,7 +28,7 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
"""Implement an Tesla binary sensor for parking and charger.""" """Implement an Tesla binary sensor for parking and charger."""
def __init__(self, tesla_device, controller, sensor_type): def __init__(self, tesla_device, controller, sensor_type):
"""Initialisation of binary sensor.""" """Initialise of a Tesla binary sensor."""
super().__init__(tesla_device, controller) super().__init__(tesla_device, controller)
self._state = False self._state = False
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id) self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)

View File

@ -6,15 +6,15 @@ https://home-assistant.io/components/binary_sensor.verisure/
""" """
import logging import logging
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.verisure import CONF_DOOR_WINDOW from homeassistant.components.verisure import CONF_DOOR_WINDOW
from homeassistant.components.verisure import HUB as hub
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Verisure binary sensors.""" """Set up the Verisure binary sensors."""
sensors = [] sensors = []
hub.update_overview() hub.update_overview()
@ -27,10 +27,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class VerisureDoorWindowSensor(BinarySensorDevice): class VerisureDoorWindowSensor(BinarySensorDevice):
"""Verisure door window sensor.""" """Representation of a Verisure door window sensor."""
def __init__(self, device_label): def __init__(self, device_label):
"""Initialize the modbus coil sensor.""" """Initialize the Verisure door window sensor."""
self._device_label = device_label self._device_label = device_label
@property @property

View File

@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vultr subscription (server) sensor.""" """Set up the Vultr subscription (server) binary sensor."""
vultr = hass.data[DATA_VULTR] vultr = hass.data[DATA_VULTR]
subscription = config.get(CONF_SUBSCRIPTION) subscription = config.get(CONF_SUBSCRIPTION)
@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if subscription not in vultr.data: if subscription not in vultr.data:
_LOGGER.error("Subscription %s not found", subscription) _LOGGER.error("Subscription %s not found", subscription)
return False return
add_devices([VultrBinarySensor(vultr, subscription, name)], True) add_devices([VultrBinarySensor(vultr, subscription, name)], True)
@ -48,7 +48,7 @@ class VultrBinarySensor(BinarySensorDevice):
"""Representation of a Vultr subscription sensor.""" """Representation of a Vultr subscription sensor."""
def __init__(self, vultr, subscription, name): def __init__(self, vultr, subscription, name):
"""Initialize a new Vultr sensor.""" """Initialize a new Vultr binary sensor."""
self._vultr = vultr self._vultr = vultr
self._name = name self._name = name

View File

@ -8,7 +8,7 @@ import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.wink import WinkDevice, DOMAIN from homeassistant.components.wink import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -16,18 +16,18 @@ DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class # These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = { SENSOR_TYPES = {
'opened': 'opening',
'brightness': 'light', 'brightness': 'light',
'vibration': 'vibration',
'loudness': 'sound',
'noise': 'sound',
'capturing_audio': 'sound', 'capturing_audio': 'sound',
'liquid_detected': 'moisture', 'capturing_video': None,
'motion': 'motion',
'presence': 'occupancy',
'co_detected': 'gas', 'co_detected': 'gas',
'liquid_detected': 'moisture',
'loudness': 'sound',
'motion': 'motion',
'noise': 'sound',
'opened': 'opening',
'presence': 'occupancy',
'smoke_detected': 'smoke', 'smoke_detected': 'smoke',
'capturing_video': None 'vibration': 'vibration',
} }
@ -103,7 +103,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Callback when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self) self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)
@property @property
@ -118,7 +118,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the device state attributes."""
return super().device_state_attributes return super().device_state_attributes
@ -127,7 +127,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the device state attributes."""
_attributes = super().device_state_attributes _attributes = super().device_state_attributes
_attributes['test_activated'] = self.wink.test_activated() _attributes['test_activated'] = self.wink.test_activated()
return _attributes return _attributes
@ -138,11 +138,18 @@ class WinkHub(WinkBinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the device state attributes."""
_attributes = super().device_state_attributes _attributes = super().device_state_attributes
_attributes['update_needed'] = self.wink.update_needed() _attributes['update_needed'] = self.wink.update_needed()
_attributes['firmware_version'] = self.wink.firmware_version() _attributes['firmware_version'] = self.wink.firmware_version()
_attributes['pairing_mode'] = self.wink.pairing_mode() _attributes['pairing_mode'] = self.wink.pairing_mode()
_kidde_code = self.wink.kidde_radio_code()
if _kidde_code is not None:
# The service call to set the Kidde code
# takes a string of 1s and 0s so it makes
# sense to display it to the user that way
_formatted_kidde_code = "{:b}".format(_kidde_code).zfill(8)
_attributes['kidde_radio_code'] = _formatted_kidde_code
return _attributes return _attributes
@ -170,7 +177,7 @@ class WinkButton(WinkBinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the device state attributes."""
_attributes = super().device_state_attributes _attributes = super().device_state_attributes
_attributes['pressed'] = self.wink.pressed() _attributes['pressed'] = self.wink.pressed()
_attributes['long_pressed'] = self.wink.long_pressed() _attributes['long_pressed'] = self.wink.long_pressed()

View File

@ -17,17 +17,21 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.8.1'] REQUIREMENTS = ['holidays==0.9.3']
# List of all countries currently supported by holidays # List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime # There seems to be no way to get the list out at runtime
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA', ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada',
'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England', 'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK',
'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE', 'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland',
'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland',
'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain', 'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales'] 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',
'Sweden', 'SE', 'UnitedKingdom', 'UK', 'UnitedStates', 'US',
'Wales']
CONF_COUNTRY = 'country' CONF_COUNTRY = 'country'
CONF_PROVINCE = 'province' CONF_PROVINCE = 'province'
CONF_WORKDAYS = 'workdays' CONF_WORKDAYS = 'workdays'

View File

@ -101,7 +101,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if DENSITY in data: if DENSITY in data:
self._density = int(data.get(DENSITY)) self._density = int(data.get(DENSITY))
@ -139,8 +139,16 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if raw_data['cmd'] == 'heartbeat':
_LOGGER.debug(
'Skipping heartbeat of the motion sensor. '
'It can introduce an incorrect state because of a firmware '
'bug (https://github.com/home-assistant/home-assistant/pull/'
'11631#issuecomment-357507744).')
return
self._should_poll = False self._should_poll = False
if NO_MOTION in data: # handle push from the hub if NO_MOTION in data: # handle push from the hub
self._no_motion_since = data[NO_MOTION] self._no_motion_since = data[NO_MOTION]
@ -186,7 +194,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
self._should_poll = False self._should_poll = False
if NO_CLOSE in data: # handle push from the hub if NO_CLOSE in data: # handle push from the hub
@ -219,7 +227,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor', XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
xiaomi_hub, 'status', 'moisture') xiaomi_hub, 'status', 'moisture')
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
self._should_poll = False self._should_poll = False
@ -256,7 +264,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if DENSITY in data: if DENSITY in data:
self._density = int(data.get(DENSITY)) self._density = int(data.get(DENSITY))
@ -293,7 +301,7 @@ class XiaomiButton(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
value = data.get(self._data_key) value = data.get(self._data_key)
if value is None: if value is None:
@ -343,7 +351,7 @@ class XiaomiCube(XiaomiBinarySensor):
attrs.update(super().device_state_attributes) attrs.update(super().device_state_attributes)
return attrs return attrs
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if 'status' in data: if 'status' in data:
self._hass.bus.fire('cube_action', { self._hass.bus.fire('cube_action', {

View File

@ -4,18 +4,18 @@ Support for WebDav Calendar.
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/calendar.caldav/ https://home-assistant.io/components/calendar.caldav/
""" """
from datetime import datetime, timedelta
import logging import logging
import re import re
from datetime import datetime, timedelta
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.calendar import ( from homeassistant.components.calendar import (
CalendarEventDevice, PLATFORM_SCHEMA) PLATFORM_SCHEMA, CalendarEventDevice)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME) CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
from homeassistant.util import dt, Throttle import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle, dt
REQUIREMENTS = ['caldav==0.5.0'] REQUIREMENTS = ['caldav==0.5.0']
@ -39,9 +39,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.Optional(CONF_CUSTOM_CALENDARS, default=[]):
vol.All(cv.ensure_list, vol.Schema([ vol.All(cv.ensure_list, vol.Schema([
vol.Schema({ vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_CALENDAR): cv.string, vol.Required(CONF_CALENDAR): cv.string,
vol.Required(CONF_SEARCH): cv.string vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_SEARCH): cv.string,
}) })
])) ]))
}) })
@ -53,12 +53,12 @@ def setup_platform(hass, config, add_devices, disc_info=None):
"""Set up the WebDav Calendar platform.""" """Set up the WebDav Calendar platform."""
import caldav import caldav
client = caldav.DAVClient(config.get(CONF_URL), url = config.get(CONF_URL)
None, username = config.get(CONF_USERNAME)
config.get(CONF_USERNAME), password = config.get(CONF_PASSWORD)
config.get(CONF_PASSWORD))
client = caldav.DAVClient(url, None, username, password)
# Retrieve all the remote calendars
calendars = client.principal().calendars() calendars = client.principal().calendars()
calendar_devices = [] calendar_devices = []
@ -70,8 +70,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
_LOGGER.debug("Ignoring calendar '%s'", calendar.name) _LOGGER.debug("Ignoring calendar '%s'", calendar.name)
continue continue
# Create additional calendars based on custom filtering # Create additional calendars based on custom filtering rules
# rules
for cust_calendar in config.get(CONF_CUSTOM_CALENDARS): for cust_calendar in config.get(CONF_CUSTOM_CALENDARS):
# Check that the base calendar matches # Check that the base calendar matches
if cust_calendar.get(CONF_CALENDAR) != calendar.name: if cust_calendar.get(CONF_CALENDAR) != calendar.name:
@ -85,12 +84,9 @@ def setup_platform(hass, config, add_devices, disc_info=None):
} }
calendar_devices.append( calendar_devices.append(
WebDavCalendarEventDevice(hass, WebDavCalendarEventDevice(
device_data, hass, device_data, calendar, True,
calendar, cust_calendar.get(CONF_SEARCH)))
True,
cust_calendar.get(CONF_SEARCH))
)
# Create a default calendar if there was no custom one # Create a default calendar if there was no custom one
if not config.get(CONF_CUSTOM_CALENDARS): if not config.get(CONF_CUSTOM_CALENDARS):
@ -102,18 +98,13 @@ def setup_platform(hass, config, add_devices, disc_info=None):
WebDavCalendarEventDevice(hass, device_data, calendar) WebDavCalendarEventDevice(hass, device_data, calendar)
) )
# Finally add all the calendars we've created
add_devices(calendar_devices) add_devices(calendar_devices)
class WebDavCalendarEventDevice(CalendarEventDevice): class WebDavCalendarEventDevice(CalendarEventDevice):
"""A device for getting the next Task from a WebDav Calendar.""" """A device for getting the next Task from a WebDav Calendar."""
def __init__(self, def __init__(self, hass, device_data, calendar, all_day=False,
hass,
device_data,
calendar,
all_day=False,
search=None): search=None):
"""Create the WebDav Calendar Event Device.""" """Create the WebDav Calendar Event Device."""
self.data = WebDavCalendarData(calendar, all_day, search) self.data = WebDavCalendarData(calendar, all_day, search)
@ -167,9 +158,7 @@ class WebDavCalendarData(object):
if vevent is None: if vevent is None:
_LOGGER.debug( _LOGGER.debug(
"No matching event found in the %d results for %s", "No matching event found in the %d results for %s",
len(results), len(results), self.calendar.name)
self.calendar.name,
)
self.event = None self.event = None
return True return True

View File

@ -4,29 +4,28 @@ Support for Todoist task management (https://todoist.com).
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/calendar.todoist/ https://home-assistant.io/components/calendar.todoist/
""" """
from datetime import datetime, timedelta
from datetime import datetime
from datetime import timedelta
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.calendar import ( from homeassistant.components.calendar import (
CalendarEventDevice, DOMAIN, PLATFORM_SCHEMA) DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice)
from homeassistant.components.google import ( from homeassistant.components.google import CONF_DEVICE_ID
CONF_DEVICE_ID) from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN
from homeassistant.const import (
CONF_ID, CONF_NAME, CONF_TOKEN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt from homeassistant.util import Throttle, dt
from homeassistant.util import Throttle
REQUIREMENTS = ['todoist-python==7.0.17'] REQUIREMENTS = ['todoist-python==7.0.17']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_EXTRA_PROJECTS = 'custom_projects'
CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
CONF_PROJECT_WHITELIST = 'include_projects'
# Calendar Platform: Does this calendar event last all day? # Calendar Platform: Does this calendar event last all day?
ALL_DAY = 'all_day' ALL_DAY = 'all_day'
# Attribute: All tasks in this project # Attribute: All tasks in this project
@ -78,20 +77,15 @@ SUMMARY = 'summary'
TASKS = 'items' TASKS = 'items'
SERVICE_NEW_TASK = 'todoist_new_task' SERVICE_NEW_TASK = 'todoist_new_task'
NEW_TASK_SERVICE_SCHEMA = vol.Schema({ NEW_TASK_SERVICE_SCHEMA = vol.Schema({
vol.Required(CONTENT): cv.string, vol.Required(CONTENT): cv.string,
vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower), vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower),
vol.Optional(LABELS): cv.ensure_list_csv, vol.Optional(LABELS): cv.ensure_list_csv,
vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
vol.Range(min=1, max=4)), vol.Optional(DUE_DATE): cv.string,
vol.Optional(DUE_DATE): cv.string
}) })
CONF_EXTRA_PROJECTS = 'custom_projects'
CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_WHITELIST = 'include_projects'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_EXTRA_PROJECTS, default=[]): vol.Optional(CONF_EXTRA_PROJECTS, default=[]):
@ -111,8 +105,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Todoist platform.""" """Set up the Todoist platform."""
# Check token:
token = config.get(CONF_TOKEN) token = config.get(CONF_TOKEN)
# Look up IDs based on (lowercase) names. # Look up IDs based on (lowercase) names.
@ -176,7 +169,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(project_devices) add_devices(project_devices)
def handle_new_task(call): def handle_new_task(call):
"""Called when a user creates a new Todoist Task from HASS.""" """Call when a user creates a new Todoist Task from HASS."""
project_name = call.data[PROJECT_NAME] project_name = call.data[PROJECT_NAME]
project_id = project_id_lookup[project_name] project_id = project_id_lookup[project_name]
@ -528,8 +521,7 @@ class TodoistProjectData(object):
# Let's set our "due date" to tomorrow # Let's set our "due date" to tomorrow
self.event[END] = { self.event[END] = {
DATETIME: ( DATETIME: (
datetime.utcnow() + datetime.utcnow() + timedelta(days=1)
timedelta(days=1)
).strftime(DATE_STR_FORMAT) ).strftime(DATE_STR_FORMAT)
} }
_LOGGER.debug("Updated %s", self._name) _LOGGER.debug("Updated %s", self._name)

View File

@ -124,15 +124,15 @@ def async_setup(hass, config):
"""Set up the camera component.""" """Set up the camera component."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.http.register_view(CameraImageView(component.entities)) hass.http.register_view(CameraImageView(component))
hass.http.register_view(CameraMjpegStream(component.entities)) hass.http.register_view(CameraMjpegStream(component))
yield from component.async_setup(config) yield from component.async_setup(config)
@callback @callback
def update_tokens(time): def update_tokens(time):
"""Update tokens of the entities.""" """Update tokens of the entities."""
for entity in component.entities.values(): for entity in component.entities:
entity.async_update_token() entity.async_update_token()
hass.async_add_job(entity.async_update_ha_state()) hass.async_add_job(entity.async_update_ha_state())
@ -358,14 +358,14 @@ class CameraView(HomeAssistantView):
requires_auth = False requires_auth = False
def __init__(self, entities): def __init__(self, component):
"""Initialize a basic camera view.""" """Initialize a basic camera view."""
self.entities = entities self.component = component
@asyncio.coroutine @asyncio.coroutine
def get(self, request, entity_id): def get(self, request, entity_id):
"""Start a GET request.""" """Start a GET request."""
camera = self.entities.get(entity_id) camera = self.component.get_entity(entity_id)
if camera is None: if camera is None:
status = 404 if request[KEY_AUTHENTICATED] else 401 status = 404 if request[KEY_AUTHENTICATED] else 401

View File

@ -75,7 +75,9 @@ class ArloCam(Camera):
self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None self._last_refresh = None
self._camera.base_station.refresh_rate = SCAN_INTERVAL.total_seconds() if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {} self.attrs = {}
def camera_image(self): def camera_image(self):

View File

@ -6,11 +6,11 @@ https://home-assistant.io/components/camera.axis/
""" """
import logging import logging
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera.mjpeg import ( from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera) CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.const import (
CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.dispatcher import dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,6 +20,7 @@ DEPENDENCIES = [DOMAIN]
def _get_image_url(host, port, mode): def _get_image_url(host, port, mode):
"""Set the URL to get the image."""
if mode == 'mjpeg': if mode == 'mjpeg':
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port) return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
elif mode == 'single': elif mode == 'single':
@ -27,34 +28,32 @@ def _get_image_url(host, port, mode):
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis camera.""" """Set up the Axis camera."""
camera_config = { camera_config = {
CONF_NAME: discovery_info[CONF_NAME], CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME], CONF_USERNAME: discovery_info[CONF_USERNAME],
CONF_PASSWORD: discovery_info[CONF_PASSWORD], CONF_PASSWORD: discovery_info[CONF_PASSWORD],
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST], CONF_MJPEG_URL: _get_image_url(
str(discovery_info[CONF_PORT]), discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
'mjpeg'), 'mjpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST], CONF_STILL_IMAGE_URL: _get_image_url(
str(discovery_info[CONF_PORT]), discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
'single'), 'single'),
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
} }
add_devices([AxisCamera(hass, add_devices([AxisCamera(
camera_config, hass, camera_config, str(discovery_info[CONF_PORT]))])
str(discovery_info[CONF_PORT]))])
class AxisCamera(MjpegCamera): class AxisCamera(MjpegCamera):
"""AxisCamera class.""" """Representation of a Axis camera."""
def __init__(self, hass, config, port): def __init__(self, hass, config, port):
"""Initialize Axis Communications camera component.""" """Initialize Axis Communications camera component."""
super().__init__(hass, config) super().__init__(hass, config)
self.port = port self.port = port
dispatcher_connect(hass, dispatcher_connect(
DOMAIN + '_' + config[CONF_NAME] + '_new_ip', hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip)
self._new_ip)
def _new_ip(self, host): def _new_ip(self, host):
"""Set new IP for video stream.""" """Set new IP for video stream."""

View File

@ -4,21 +4,21 @@ Support for Blink system camera.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/ https://home-assistant.io/components/camera.blink/
""" """
from datetime import timedelta
import logging import logging
from datetime import timedelta
import requests import requests
from homeassistant.components.blink import DOMAIN from homeassistant.components.blink import DOMAIN
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
from homeassistant.util import Throttle from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['blink'] DEPENDENCIES = ['blink']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Blink Camera.""" """Set up a Blink Camera."""
@ -45,7 +45,7 @@ class BlinkCamera(Camera):
self.notifications = self.data.cameras[self._name].notifications self.notifications = self.data.cameras[self._name].notifications
self.response = None self.response = None
_LOGGER.info("Initialized blink camera %s", self._name) _LOGGER.debug("Initialized blink camera %s", self._name)
@property @property
def name(self): def name(self):
@ -55,7 +55,7 @@ class BlinkCamera(Camera):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def request_image(self): def request_image(self):
"""Request a new image from Blink servers.""" """Request a new image from Blink servers."""
_LOGGER.info("Requesting new image from blink servers") _LOGGER.debug("Requesting new image from blink servers")
image_url = self.check_for_motion() image_url = self.check_for_motion()
header = self.data.cameras[self._name].header header = self.data.cameras[self._name].header
self.response = requests.get(image_url, headers=header, stream=True) self.response = requests.get(image_url, headers=header, stream=True)
@ -68,7 +68,7 @@ class BlinkCamera(Camera):
# We detected motion at some point # We detected motion at some point
self.data.last_motion() self.data.last_motion()
self.notifications = notifs self.notifications = notifs
# returning motion image currently not working # Returning motion image currently not working
# return self.data.cameras[self._name].motion['image'] # return self.data.cameras[self._name].motion['image']
elif notifs < self.notifications: elif notifs < self.notifications:
self.notifications = notifs self.notifications = notifs

View File

@ -44,6 +44,8 @@ class FoscamCam(Camera):
def __init__(self, device_info): def __init__(self, device_info):
"""Initialize a Foscam camera.""" """Initialize a Foscam camera."""
from libpyfoscam import FoscamCamera
super(FoscamCam, self).__init__() super(FoscamCam, self).__init__()
ip_address = device_info.get(CONF_IP) ip_address = device_info.get(CONF_IP)
@ -53,10 +55,8 @@ class FoscamCam(Camera):
self._name = device_info.get(CONF_NAME) self._name = device_info.get(CONF_NAME)
self._motion_status = False self._motion_status = False
from libpyfoscam import FoscamCamera self._foscam_session = FoscamCamera(
ip_address, port, self._username, self._password, verbose=False)
self._foscam_session = FoscamCamera(ip_address, port, self._username,
self._password, verbose=False)
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
@ -75,20 +75,20 @@ class FoscamCam(Camera):
def enable_motion_detection(self): def enable_motion_detection(self):
"""Enable motion detection in camera.""" """Enable motion detection in camera."""
ret, err = self._foscam_session.enable_motion_detection() try:
if ret == FOSCAM_COMM_ERROR: ret = self._foscam_session.enable_motion_detection()
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err) self._motion_status = ret == FOSCAM_COMM_ERROR
self._motion_status = True except TypeError:
else: _LOGGER.debug("Communication problem")
self._motion_status = False self._motion_status = False
def disable_motion_detection(self): def disable_motion_detection(self):
"""Disable motion detection.""" """Disable motion detection."""
ret, err = self._foscam_session.disable_motion_detection() try:
if ret == FOSCAM_COMM_ERROR: ret = self._foscam_session.disable_motion_detection()
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err) self._motion_status = ret == FOSCAM_COMM_ERROR
self._motion_status = True except TypeError:
else: _LOGGER.debug("Communication problem")
self._motion_status = False self._motion_status = False
@property @property

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import ( from homeassistant.components.ffmpeg import (
DATA_FFMPEG) DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import ( from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream) async_aiohttp_proxy_stream)
@ -31,6 +31,7 @@ DEFAULT_NAME = 'ONVIF Camera'
DEFAULT_PORT = 5000 DEFAULT_PORT = 5000
DEFAULT_USERNAME = 'admin' DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888' DEFAULT_PASSWORD = '888888'
DEFAULT_ARGUMENTS = '-q:v 2'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
}) })
@ -59,7 +61,7 @@ class ONVIFCamera(Camera):
super().__init__() super().__init__()
self._name = config.get(CONF_NAME) self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = '-q:v 2' self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
media = ONVIFService( media = ONVIFService(
'http://{}:{}/onvif/device_service'.format( 'http://{}:{}/onvif/device_service'.format(
config.get(CONF_HOST), config.get(CONF_PORT)), config.get(CONF_HOST), config.get(CONF_PORT)),

View File

@ -0,0 +1,113 @@
"""
Support for Xeoma Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.xeoma/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.2']
_LOGGER = logging.getLogger(__name__)
CONF_CAMERAS = 'cameras'
CONF_HIDE = 'hide'
CONF_IMAGE_NAME = 'image_name'
CONF_NEW_VERSION = 'new_version'
CAMERAS_SCHEMA = vol.Schema({
vol.Required(CONF_IMAGE_NAME): cv.string,
vol.Optional(CONF_HIDE, default=False): cv.boolean,
vol.Optional(CONF_NAME): cv.string,
}, required=False)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_CAMERAS, default={}):
vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])),
vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
})
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Discover and setup Xeoma Cameras."""
from pyxeoma.xeoma import Xeoma, XeomaError
host = config[CONF_HOST]
login = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
new_version = config[CONF_NEW_VERSION]
xeoma = Xeoma(host, new_version, login, password)
try:
yield from xeoma.async_test_connection()
discovered_image_names = yield from xeoma.async_get_image_names()
discovered_cameras = [
{
CONF_IMAGE_NAME: image_name,
CONF_HIDE: False,
CONF_NAME: image_name
}
for image_name in discovered_image_names
]
for cam in config[CONF_CAMERAS]:
camera = next(
(dc for dc in discovered_cameras
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)
if camera is not None:
if CONF_NAME in cam:
camera[CONF_NAME] = cam[CONF_NAME]
if CONF_HIDE in cam:
camera[CONF_HIDE] = cam[CONF_HIDE]
cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras))
async_add_devices(
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME])
for camera in cameras])
except XeomaError as err:
_LOGGER.error("Error: %s", err.message)
return
class XeomaCamera(Camera):
"""Implementation of a Xeoma camera."""
def __init__(self, xeoma, image, name):
"""Initialize a Xeoma camera."""
super().__init__()
self._xeoma = xeoma
self._name = name
self._image = image
self._last_image = None
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from pyxeoma.xeoma import XeomaError
try:
image = yield from self._xeoma.async_get_camera_image(self._image)
self._last_image = image
except XeomaError as err:
_LOGGER.error("Error fetching image: %s", err.message)
return self._last_image
@property
def name(self):
"""Return the name of this device."""
return self._name

View File

@ -499,53 +499,54 @@ class ClimateDevice(Entity):
self.precision), self.precision),
} }
supported_features = self.supported_features
if self.target_temperature_step is not None: if self.target_temperature_step is not None:
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
target_temp_high = self.target_temperature_high if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = show_temp( data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit, self.hass, self.target_temperature_high, self.temperature_unit,
self.precision) self.precision)
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
data[ATTR_TARGET_TEMP_LOW] = show_temp( data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit, self.hass, self.target_temperature_low, self.temperature_unit,
self.precision) self.precision)
humidity = self.target_humidity if supported_features & SUPPORT_TARGET_HUMIDITY:
if humidity is not None: data[ATTR_HUMIDITY] = self.target_humidity
data[ATTR_HUMIDITY] = humidity
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
data[ATTR_MIN_HUMIDITY] = self.min_humidity data[ATTR_MIN_HUMIDITY] = self.min_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
data[ATTR_MAX_HUMIDITY] = self.max_humidity data[ATTR_MAX_HUMIDITY] = self.max_humidity
fan_mode = self.current_fan_mode if supported_features & SUPPORT_FAN_MODE:
if fan_mode is not None: data[ATTR_FAN_MODE] = self.current_fan_mode
data[ATTR_FAN_MODE] = fan_mode
if self.fan_list: if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list data[ATTR_FAN_LIST] = self.fan_list
operation_mode = self.current_operation if supported_features & SUPPORT_OPERATION_MODE:
if operation_mode is not None: data[ATTR_OPERATION_MODE] = self.current_operation
data[ATTR_OPERATION_MODE] = operation_mode
if self.operation_list: if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list data[ATTR_OPERATION_LIST] = self.operation_list
is_hold = self.current_hold_mode if supported_features & SUPPORT_HOLD_MODE:
if is_hold is not None: data[ATTR_HOLD_MODE] = self.current_hold_mode
data[ATTR_HOLD_MODE] = is_hold
swing_mode = self.current_swing_mode if supported_features & SUPPORT_SWING_MODE:
if swing_mode is not None: data[ATTR_SWING_MODE] = self.current_swing_mode
data[ATTR_SWING_MODE] = swing_mode
if self.swing_list: if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list data[ATTR_SWING_LIST] = self.swing_list
if supported_features & SUPPORT_AWAY_MODE:
is_away = self.is_away_mode_on is_away = self.is_away_mode_on
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
if supported_features & SUPPORT_AUX_HEAT:
is_aux_heat = self.is_aux_heat_on is_aux_heat = self.is_aux_heat_on
if is_aux_heat is not None:
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
return data return data

View File

@ -9,35 +9,23 @@ import re
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_FAN_MODE, ATTR_SWING_MODE, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE,
ATTR_CURRENT_TEMPERATURE, ClimateDevice, PLATFORM_SCHEMA, ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
SUPPORT_SWING_MODE, STATE_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
STATE_DRY, STATE_FAN_ONLY ClimateDevice)
)
from homeassistant.components.daikin import ( from homeassistant.components.daikin import (
daikin_api_setup, ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
ATTR_TARGET_TEMPERATURE, daikin_api_setup)
ATTR_INSIDE_TEMPERATURE,
ATTR_OUTSIDE_TEMPERATURE
)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_NAME, ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
TEMP_CELSIUS, import homeassistant.helpers.config_validation as cv
ATTR_TEMPERATURE
)
REQUIREMENTS = ['pydaikin==0.4'] REQUIREMENTS = ['pydaikin==0.4']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE |
SUPPORT_SWING_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=None): cv.string, vol.Optional(CONF_NAME, default=None): cv.string,
@ -56,19 +44,22 @@ HA_ATTR_TO_DAIKIN = {
ATTR_OPERATION_MODE: 'mode', ATTR_OPERATION_MODE: 'mode',
ATTR_FAN_MODE: 'f_rate', ATTR_FAN_MODE: 'f_rate',
ATTR_SWING_MODE: 'f_dir', ATTR_SWING_MODE: 'f_dir',
ATTR_INSIDE_TEMPERATURE: 'htemp',
ATTR_OUTSIDE_TEMPERATURE: 'otemp',
ATTR_TARGET_TEMPERATURE: 'stemp'
} }
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Daikin HVAC platform.""" """Set up the Daikin HVAC platform."""
if discovery_info is not None: if discovery_info is not None:
host = discovery_info.get('ip') host = discovery_info.get('ip')
name = None name = None
_LOGGER.info("Discovered a Daikin AC on %s", host) _LOGGER.debug("Discovered a Daikin AC on %s", host)
else: else:
host = config.get(CONF_HOST) host = config.get(CONF_HOST)
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
_LOGGER.info("Added Daikin AC on %s", host) _LOGGER.debug("Added Daikin AC on %s", host)
api = daikin_api_setup(hass, host, name) api = daikin_api_setup(hass, host, name)
add_devices([DaikinClimate(api)], True) add_devices([DaikinClimate(api)], True)
@ -101,6 +92,17 @@ class DaikinClimate(ClimateDevice):
), ),
} }
self._supported_features = SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_OPERATION_MODE
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE]
if self._api.device.values.get(daikin_attr) is not None:
self._supported_features |= SUPPORT_FAN_MODE
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]
if self._api.device.values.get(daikin_attr) is not None:
self._supported_features |= SUPPORT_SWING_MODE
def get(self, key): def get(self, key):
"""Retrieve device settings from API library cache.""" """Retrieve device settings from API library cache."""
value = None value = None
@ -108,29 +110,34 @@ class DaikinClimate(ClimateDevice):
if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE, if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE,
ATTR_CURRENT_TEMPERATURE]: ATTR_CURRENT_TEMPERATURE]:
value = self._api.device.values.get('htemp') key = ATTR_INSIDE_TEMPERATURE
daikin_attr = HA_ATTR_TO_DAIKIN.get(key)
if key == ATTR_INSIDE_TEMPERATURE:
value = self._api.device.values.get(daikin_attr)
cast_to_float = True cast_to_float = True
if key == ATTR_TARGET_TEMPERATURE: elif key == ATTR_TARGET_TEMPERATURE:
value = self._api.device.values.get('stemp') value = self._api.device.values.get(daikin_attr)
cast_to_float = True cast_to_float = True
elif key == ATTR_OUTSIDE_TEMPERATURE: elif key == ATTR_OUTSIDE_TEMPERATURE:
value = self._api.device.values.get('otemp') value = self._api.device.values.get(daikin_attr)
cast_to_float = True cast_to_float = True
elif key == ATTR_FAN_MODE: elif key == ATTR_FAN_MODE:
value = self._api.device.represent('f_rate')[1].title() value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_SWING_MODE: elif key == ATTR_SWING_MODE:
value = self._api.device.represent('f_dir')[1].title() value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_OPERATION_MODE: elif key == ATTR_OPERATION_MODE:
# Daikin can return also internal states auto-1 or auto-7 # Daikin can return also internal states auto-1 or auto-7
# and we need to translate them as AUTO # and we need to translate them as AUTO
value = re.sub( value = re.sub(
'[^a-z]', '[^a-z]',
'', '',
self._api.device.represent('mode')[1] self._api.device.represent(daikin_attr)[1]
).title() ).title()
if value is None: if value is None:
_LOGGER.warning("Invalid value requested for key %s", key) _LOGGER.error("Invalid value requested for key %s", key)
else: else:
if value == "-" or value == "--": if value == "-" or value == "--":
value = None value = None
@ -178,7 +185,7 @@ class DaikinClimate(ClimateDevice):
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS return self._supported_features
@property @property
def name(self): def name(self):

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/demo/
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE, SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
@ -14,6 +15,7 @@ from homeassistant.components.climate import (
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY | SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT |
SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH | SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH |

View File

@ -10,16 +10,12 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
DOMAIN, DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS,
PLATFORM_SCHEMA, STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE,
STATE_ECO, STATE_GAS, STATE_ELECTRIC, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
STATE_HEAT_PUMP, STATE_HIGH_DEMAND, from homeassistant.const import (
STATE_OFF, SUPPORT_TARGET_TEMPERATURE, ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
SUPPORT_OPERATION_MODE, TEMP_FAHRENHEIT)
ClimateDevice)
from homeassistant.const import (ATTR_ENTITY_ID,
CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyeconet==0.0.4'] REQUIREMENTS = ['pyeconet==0.0.4']
@ -59,6 +55,7 @@ HA_STATE_TO_ECONET = {
STATE_GAS: 'gas', STATE_GAS: 'gas',
STATE_HIGH_DEMAND: 'High Demand', STATE_HIGH_DEMAND: 'High Demand',
STATE_OFF: 'Off', STATE_OFF: 'Off',
STATE_PERFORMANCE: 'Performance'
} }
ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()} ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()}
@ -87,7 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters) hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters)
def service_handle(service): def service_handle(service):
"""Handler for services.""" """Handle the service calls."""
entity_ids = service.data.get('entity_id') entity_ids = service.data.get('entity_id')
all_heaters = hass.data[ECONET_DATA]['water_heaters'] all_heaters = hass.data[ECONET_DATA]['water_heaters']
_heaters = [ _heaters = [
@ -105,12 +102,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_water_heater.schedule_update_ha_state(True) _water_heater.schedule_update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_ADD_VACATION, hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle,
service_handle,
schema=ADD_VACATION_SCHEMA) schema=ADD_VACATION_SCHEMA)
hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle,
service_handle,
schema=DELETE_VACATION_SCHEMA) schema=DELETE_VACATION_SCHEMA)
@ -138,7 +133,7 @@ class EcoNetWaterHeater(ClimateDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional device state attributes."""
data = {} data = {}
vacations = self.water_heater.get_vacations() vacations = self.water_heater.get_vacations()
if vacations: if vacations:
@ -157,8 +152,7 @@ class EcoNetWaterHeater(ClimateDevice):
""" """
Return current operation as one of the following. Return current operation as one of the following.
["eco", "heat_pump", ["eco", "heat_pump", "high_demand", "electric_only"]
"high_demand", "electric_only"]
""" """
current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode) current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode)
return current_op return current_op
@ -189,7 +183,7 @@ class EcoNetWaterHeater(ClimateDevice):
if target_temp is not None: if target_temp is not None:
self.water_heater.set_target_set_point(target_temp) self.water_heater.set_target_set_point(target_temp)
else: else:
_LOGGER.error("A target temperature must be provided.") _LOGGER.error("A target temperature must be provided")
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
@ -197,7 +191,7 @@ class EcoNetWaterHeater(ClimateDevice):
if op_mode_to_set is not None: if op_mode_to_set is not None:
self.water_heater.set_mode(op_mode_to_set) self.water_heater.set_mode(op_mode_to_set)
else: else:
_LOGGER.error("An operation mode must be provided.") _LOGGER.error("An operation mode must be provided")
def add_vacation(self, start, end): def add_vacation(self, start, end):
"""Add a vacation to this water heater.""" """Add a vacation to this water heater."""

View File

@ -9,9 +9,10 @@ from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT) ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ( from homeassistant.const import (
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD) TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyephember==0.1.1'] REQUIREMENTS = ['pyephember==0.1.1']
@ -59,8 +60,11 @@ class EphEmberThermostat(ClimateDevice):
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
if self._hot_water:
return SUPPORT_AUX_HEAT return SUPPORT_AUX_HEAT
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT
@property @property
def name(self): def name(self):
"""Return the name of the thermostat, if any.""" """Return the name of the thermostat, if any."""
@ -81,6 +85,14 @@ class EphEmberThermostat(ClimateDevice):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._zone['targetTemperature'] return self._zone['targetTemperature']
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
if self._hot_water:
return None
return 1
@property @property
def current_operation(self): def current_operation(self):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
@ -105,18 +117,39 @@ class EphEmberThermostat(ClimateDevice):
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return return
if self._hot_water:
return
if temperature == self.target_temperature:
return
if temperature > self.max_temp or temperature < self.min_temp:
return
self._ember.set_target_temperture_by_name(self._zone_name,
int(temperature))
@property @property
def min_temp(self): def min_temp(self):
"""Return the minimum temperature.""" """Return the minimum temperature."""
# Hot water temp doesn't support being changed
if self._hot_water:
return self._zone['targetTemperature'] return self._zone['targetTemperature']
return 5
@property @property
def max_temp(self): def max_temp(self):
"""Return the maximum temperature.""" """Return the maximum temperature."""
if self._hot_water:
return self._zone['targetTemperature'] return self._zone['targetTemperature']
return 35
def update(self): def update(self):
"""Get the latest data.""" """Get the latest data."""
self._zone = self._ember.get_zone(self._zone_name) self._zone = self._ember.get_zone(self._zone_name)

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES) CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.6'] REQUIREMENTS = ['python-eq3bt==0.1.8']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -17,7 +17,8 @@ from homeassistant.components.climate import (
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA) SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_UNKNOWN)
from homeassistant.helpers import condition from homeassistant.helpers import condition
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval) async_track_state_change, async_track_time_interval)
@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor']
DEFAULT_TOLERANCE = 0.3 DEFAULT_TOLERANCE = 0.3
DEFAULT_NAME = 'Generic Thermostat' DEFAULT_NAME = 'Generic Thermostat'
DEFAULT_AWAY_TEMP = 16
CONF_HEATER = 'heater' CONF_HEATER = 'heater'
CONF_SENSOR = 'target_sensor' CONF_SENSOR = 'target_sensor'
@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive' CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode' CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
CONF_AWAY_TEMP = 'away_temp' CONF_AWAY_TEMP = 'away_temp'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE | SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE) SUPPORT_OPERATION_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta), cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE): vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF]), vol.In([STATE_AUTO, STATE_OFF]),
vol.Optional(CONF_AWAY_TEMP, vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
default=DEFAULT_AWAY_TEMP): vol.Coerce(float)
}) })
@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice):
self._operation_list = [STATE_HEAT, STATE_OFF] self._operation_list = [STATE_HEAT, STATE_OFF]
if initial_operation_mode == STATE_OFF: if initial_operation_mode == STATE_OFF:
self._enabled = False self._enabled = False
self._current_operation = STATE_OFF
else: else:
self._enabled = True self._enabled = True
self._active = False self._active = False
@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice):
self._max_temp = max_temp self._max_temp = max_temp
self._target_temp = target_temp self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit self._unit = hass.config.units.temperature_unit
self._support_flags = SUPPORT_FLAGS
if away_temp is not None:
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
self._away_temp = away_temp self._away_temp = away_temp
self._is_away = False self._is_away = False
@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice):
async_track_time_interval( async_track_time_interval(
hass, self._async_keep_alive, self._keep_alive) hass, self._async_keep_alive, self._keep_alive)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state and sensor_state.state != STATE_UNKNOWN:
self._async_update_temp(sensor_state)
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Run when entity about to be added.""" """Run when entity about to be added."""
@ -154,19 +161,29 @@ class GenericThermostat(ClimateDevice):
self._target_temp = self.max_temp self._target_temp = self.max_temp
else: else:
self._target_temp = self.min_temp self._target_temp = self.min_temp
_LOGGER.warning('Undefined target temperature, \ _LOGGER.warning("Undefined target temperature,"
falling back to %s', self._target_temp) "falling back to %s", self._target_temp)
else: else:
self._target_temp = float( self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE]) old_state.attributes[ATTR_TEMPERATURE])
self._is_away = True if str( if old_state.attributes[ATTR_AWAY_MODE] is not None:
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False self._is_away = str(
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF: old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
self._current_operation = STATE_OFF if (self._initial_operation_mode is None and
self._enabled = False old_state.attributes[ATTR_OPERATION_MODE] is not None):
if self._initial_operation_mode is None: self._current_operation = \
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF: old_state.attributes[ATTR_OPERATION_MODE]
self._enabled = False if self._current_operation != STATE_OFF:
self._enabled = True
else:
# No previous state, try and restore defaults
if self._target_temp is None:
if self.ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning("No previously saved temperature, setting to %s",
self._target_temp)
@property @property
def state(self): def state(self):
@ -230,7 +247,7 @@ class GenericThermostat(ClimateDevice):
if self._is_device_active: if self._is_device_active:
self._heater_turn_off() self._heater_turn_off()
else: else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode) _LOGGER.error("Unrecognized operation mode: %s", operation_mode)
return return
# Ensure we updae the current operation after changing the mode # Ensure we updae the current operation after changing the mode
self.schedule_update_ha_state() self.schedule_update_ha_state()
@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice):
self._cur_temp = self.hass.config.units.temperature( self._cur_temp = self.hass.config.units.temperature(
float(state.state), unit) float(state.state), unit)
except ValueError as ex: except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex) _LOGGER.error("Unable to update from sensor: %s", ex)
@callback @callback
def _async_control_heating(self): def _async_control_heating(self):
@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice):
if not self._active and None not in (self._cur_temp, if not self._active and None not in (self._cur_temp,
self._target_temp): self._target_temp):
self._active = True self._active = True
_LOGGER.info('Obtained current and target temperature. ' _LOGGER.info("Obtained current and target temperature. "
'Generic thermostat active.') "Generic thermostat active. %s, %s",
self._cur_temp, self._target_temp)
if not self._active: if not self._active:
return return
@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp >= \ too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance self._cold_tolerance
if too_cold: if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id) _LOGGER.info("Turning off AC %s", self.heater_entity_id)
self._heater_turn_off() self._heater_turn_off()
else: else:
too_hot = self._cur_temp - self._target_temp >= \ too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance self._hot_tolerance
if too_hot: if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id) _LOGGER.info("Turning on AC %s", self.heater_entity_id)
self._heater_turn_on() self._heater_turn_on()
else: else:
is_heating = self._is_device_active is_heating = self._is_device_active
@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice):
too_hot = self._cur_temp - self._target_temp >= \ too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance self._hot_tolerance
if too_hot: if too_hot:
_LOGGER.info('Turning off heater %s', _LOGGER.info("Turning off heater %s",
self.heater_entity_id) self.heater_entity_id)
self._heater_turn_off() self._heater_turn_off()
else: else:
too_cold = self._target_temp - self._cur_temp >= \ too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance self._cold_tolerance
if too_cold: if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id) _LOGGER.info("Turning on heater %s", self.heater_entity_id)
self._heater_turn_on() self._heater_turn_on()
@property @property
@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice):
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS return self._support_flags
@callback @callback
def _heater_turn_on(self): def _heater_turn_on(self):

View File

@ -5,13 +5,14 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.knx/ https://home-assistant.io/components/climate.knx/
""" """
import asyncio import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.climate import ( from homeassistant.components.climate import (
PLATFORM_SCHEMA, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE) ClimateDevice)
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -61,24 +62,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
discovery_info=None):
"""Set up climate(s) for KNX platform.""" """Set up climate(s) for KNX platform."""
if DATA_KNX not in hass.data \ if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
or not hass.data[DATA_KNX].initialized: return
return False
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:
async_add_devices_config(hass, config, async_add_devices) async_add_devices_config(hass, config, async_add_devices)
return True
@callback @callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices): def async_add_devices_discovery(hass, discovery_info, async_add_devices):
"""Set up climates for KNX platform configured within plattform.""" """Set up climates for KNX platform configured within platform."""
entities = [] entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]: for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name] device = hass.data[DATA_KNX].xknx.devices[device_name]
@ -88,28 +85,22 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback @callback
def async_add_devices_config(hass, config, async_add_devices): def async_add_devices_config(hass, config, async_add_devices):
"""Set up climate for KNX platform configured within plattform.""" """Set up climate for KNX platform configured within platform."""
import xknx import xknx
climate = xknx.devices.Climate( climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx, hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME), name=config.get(CONF_NAME),
group_address_temperature=config.get( group_address_temperature=config.get(CONF_TEMPERATURE_ADDRESS),
CONF_TEMPERATURE_ADDRESS),
group_address_target_temperature=config.get( group_address_target_temperature=config.get(
CONF_TARGET_TEMPERATURE_ADDRESS), CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_setpoint_shift=config.get( group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift_state=config.get( group_address_setpoint_shift_state=config.get(
CONF_SETPOINT_SHIFT_STATE_ADDRESS), CONF_SETPOINT_SHIFT_STATE_ADDRESS),
setpoint_shift_step=config.get( setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP),
CONF_SETPOINT_SHIFT_STEP), setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_max=config.get( setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN),
CONF_SETPOINT_SHIFT_MAX), group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
setpoint_shift_min=config.get(
CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(
CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get( group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS), CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get( group_address_controller_status=config.get(
@ -127,10 +118,10 @@ def async_add_devices_config(hass, config, async_add_devices):
class KNXClimate(ClimateDevice): class KNXClimate(ClimateDevice):
"""Representation of a KNX climate.""" """Representation of a KNX climate device."""
def __init__(self, hass, device): def __init__(self, hass, device):
"""Initialization of KNXClimate.""" """Initialize of a KNX climate device."""
self.device = device self.device = device
self.hass = hass self.hass = hass
self.async_register_callbacks() self.async_register_callbacks()
@ -149,7 +140,7 @@ class KNXClimate(ClimateDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
@asyncio.coroutine @asyncio.coroutine
def after_update_callback(device): def after_update_callback(device):
"""Callback after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument # pylint: disable=unused-argument
yield from self.async_update_ha_state() yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)

View File

@ -19,7 +19,7 @@ from homeassistant.components.climate import (
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT) SUPPORT_AUX_HEAT)
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME) STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE, CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability) CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability)
@ -35,21 +35,30 @@ DEFAULT_NAME = 'MQTT HVAC'
CONF_POWER_COMMAND_TOPIC = 'power_command_topic' CONF_POWER_COMMAND_TOPIC = 'power_command_topic'
CONF_POWER_STATE_TOPIC = 'power_state_topic' CONF_POWER_STATE_TOPIC = 'power_state_topic'
CONF_POWER_STATE_TEMPLATE = 'power_state_template'
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic' CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
CONF_MODE_STATE_TOPIC = 'mode_state_topic' CONF_MODE_STATE_TOPIC = 'mode_state_topic'
CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic' CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic'
CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic' CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic'
CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template'
CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic' CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic'
CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic' CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template'
CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic' CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic'
CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic' CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic'
CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template'
CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic' CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic'
CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic' CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic'
CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template'
CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic' CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
CONF_HOLD_STATE_TOPIC = 'hold_state_topic' CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
CONF_AUX_COMMAND_TOPIC = 'aux_command_topic' CONF_AUX_COMMAND_TOPIC = 'aux_command_topic'
CONF_AUX_STATE_TOPIC = 'aux_state_topic' CONF_AUX_STATE_TOPIC = 'aux_state_topic'
CONF_AUX_STATE_TEMPLATE = 'aux_state_template'
CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template'
CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic' CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic'
CONF_PAYLOAD_ON = 'payload_on' CONF_PAYLOAD_ON = 'payload_on'
@ -71,6 +80,7 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic,
@ -79,6 +89,18 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template,
vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC): vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC):
mqtt.valid_subscribe_topic, mqtt.valid_subscribe_topic,
vol.Optional(CONF_FAN_MODE_LIST, vol.Optional(CONF_FAN_MODE_LIST,
@ -100,6 +122,26 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT climate devices.""" """Set up the MQTT climate devices."""
template_keys = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
CONF_TEMPERATURE_STATE_TEMPLATE,
CONF_FAN_MODE_STATE_TEMPLATE,
CONF_SWING_MODE_STATE_TEMPLATE,
CONF_AWAY_MODE_STATE_TEMPLATE,
CONF_HOLD_STATE_TEMPLATE,
CONF_AUX_STATE_TEMPLATE,
CONF_CURRENT_TEMPERATURE_TEMPLATE
)
value_templates = {}
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
value_templates = {key: value_template for key in template_keys}
for key in template_keys & config.keys():
value_templates[key] = config.get(key)
value_templates[key].hass = hass
async_add_devices([ async_add_devices([
MqttClimate( MqttClimate(
hass, hass,
@ -125,6 +167,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
CONF_CURRENT_TEMPERATURE_TOPIC CONF_CURRENT_TEMPERATURE_TOPIC
) )
}, },
value_templates,
config.get(CONF_QOS), config.get(CONF_QOS),
config.get(CONF_RETAIN), config.get(CONF_RETAIN),
config.get(CONF_MODE_LIST), config.get(CONF_MODE_LIST),
@ -145,18 +188,19 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class MqttClimate(MqttAvailability, ClimateDevice): class MqttClimate(MqttAvailability, ClimateDevice):
"""Representation of a demo climate device.""" """Representation of a demo climate device."""
def __init__(self, hass, name, topic, qos, retain, mode_list, def __init__(self, hass, name, topic, value_templates, qos, retain,
fan_mode_list, swing_mode_list, target_temperature, away, mode_list, fan_mode_list, swing_mode_list,
hold, current_fan_mode, current_swing_mode, target_temperature, away, hold, current_fan_mode,
current_operation, aux, send_if_off, payload_on, current_swing_mode, current_operation, aux, send_if_off,
payload_off, availability_topic, payload_available, payload_on, payload_off, availability_topic,
payload_not_available): payload_available, payload_not_available):
"""Initialize the climate device.""" """Initialize the climate device."""
super().__init__(availability_topic, qos, payload_available, super().__init__(availability_topic, qos, payload_available,
payload_not_available) payload_not_available)
self.hass = hass self.hass = hass
self._name = name self._name = name
self._topic = topic self._topic = topic
self._value_templates = value_templates
self._qos = qos self._qos = qos
self._retain = retain self._retain = retain
self._target_temperature = target_temperature self._target_temperature = target_temperature
@ -184,6 +228,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_current_temp_received(topic, payload, qos): def handle_current_temp_received(topic, payload, qos):
"""Handle current temperature coming via MQTT.""" """Handle current temperature coming via MQTT."""
if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates:
payload =\
self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\
async_render_with_possible_json_value(payload)
try: try:
self._current_temperature = float(payload) self._current_temperature = float(payload)
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@ -198,6 +247,10 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_mode_received(topic, payload, qos): def handle_mode_received(topic, payload, qos):
"""Handle receiving mode via MQTT.""" """Handle receiving mode via MQTT."""
if CONF_MODE_STATE_TEMPLATE in self._value_templates:
payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._operation_list: if payload not in self._operation_list:
_LOGGER.error("Invalid mode: %s", payload) _LOGGER.error("Invalid mode: %s", payload)
else: else:
@ -212,6 +265,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_temperature_received(topic, payload, qos): def handle_temperature_received(topic, payload, qos):
"""Handle target temperature coming via MQTT.""" """Handle target temperature coming via MQTT."""
if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates:
payload = \
self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
try: try:
self._target_temperature = float(payload) self._target_temperature = float(payload)
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@ -226,6 +284,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_fan_mode_received(topic, payload, qos): def handle_fan_mode_received(topic, payload, qos):
"""Handle receiving fan mode via MQTT.""" """Handle receiving fan mode via MQTT."""
if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates:
payload = \
self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._fan_list: if payload not in self._fan_list:
_LOGGER.error("Invalid fan mode: %s", payload) _LOGGER.error("Invalid fan mode: %s", payload)
else: else:
@ -240,6 +303,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_swing_mode_received(topic, payload, qos): def handle_swing_mode_received(topic, payload, qos):
"""Handle receiving swing mode via MQTT.""" """Handle receiving swing mode via MQTT."""
if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates:
payload = \
self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._swing_list: if payload not in self._swing_list:
_LOGGER.error("Invalid swing mode: %s", payload) _LOGGER.error("Invalid swing mode: %s", payload)
else: else:
@ -254,6 +322,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_away_mode_received(topic, payload, qos): def handle_away_mode_received(topic, payload, qos):
"""Handle receiving away mode via MQTT.""" """Handle receiving away mode via MQTT."""
if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates:
payload = \
self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload == "True":
payload = self._payload_on
elif payload == "False":
payload = self._payload_off
if payload == self._payload_on: if payload == self._payload_on:
self._away = True self._away = True
elif payload == self._payload_off: elif payload == self._payload_off:
@ -271,6 +348,14 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_aux_mode_received(topic, payload, qos): def handle_aux_mode_received(topic, payload, qos):
"""Handle receiving aux mode via MQTT.""" """Handle receiving aux mode via MQTT."""
if CONF_AUX_STATE_TEMPLATE in self._value_templates:
payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload == "True":
payload = self._payload_on
elif payload == "False":
payload = self._payload_off
if payload == self._payload_on: if payload == self._payload_on:
self._aux = True self._aux = True
elif payload == self._payload_off: elif payload == self._payload_off:
@ -288,6 +373,10 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@callback @callback
def handle_hold_mode_received(topic, payload, qos): def handle_hold_mode_received(topic, payload, qos):
"""Handle receiving hold mode via MQTT.""" """Handle receiving hold mode via MQTT."""
if CONF_HOLD_STATE_TEMPLATE in self._value_templates:
payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
self._hold = payload self._hold = payload
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()

View File

@ -7,9 +7,10 @@ https://home-assistant.io/components/climate.mysensors/
from homeassistant.components import mysensors from homeassistant.components import mysensors
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice, STATE_COOL, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE) SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
ClimateDevice)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
DICT_HA_TO_MYS = { DICT_HA_TO_MYS = {
@ -31,7 +32,7 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors climate.""" """Set up the MySensors climate."""
mysensors.setup_mysensors_platform( mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices) hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices)
@ -52,8 +53,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
@property @property
def temperature_unit(self): def temperature_unit(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
return (TEMP_CELSIUS return TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT
if self.gateway.metric else TEMP_FAHRENHEIT)
@property @property
def current_temperature(self): def current_temperature(self):
@ -139,7 +139,8 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value) self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic: if self.gateway.optimistic:
# optimistically assume that device has changed state # O
# ptimistically assume that device has changed state
self._values[value_type] = value self._values[value_type] = value
self.schedule_update_ha_state() self.schedule_update_ha_state()
@ -149,7 +150,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan) self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
if self.gateway.optimistic: if self.gateway.optimistic:
# optimistically assume that device has changed state # Optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan self._values[set_req.V_HVAC_SPEED] = fan
self.schedule_update_ha_state() self.schedule_update_ha_state()
@ -159,7 +160,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self.node_id, self.child_id, self.value_type, self.node_id, self.child_id, self.value_type,
DICT_HA_TO_MYS[operation_mode]) DICT_HA_TO_MYS[operation_mode])
if self.gateway.optimistic: if self.gateway.optimistic:
# optimistically assume that device has changed state # Optimistically assume that device has changed state
self._values[self.value_type] = operation_mode self._values[self.value_type] = operation_mode
self.schedule_update_ha_state() self.schedule_update_ha_state()

View File

@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components.nest import DATA_NEST from homeassistant.components.nest import DATA_NEST
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE, ATTR_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
@ -27,8 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=1)), vol.All(vol.Coerce(int), vol.Range(min=1)),
}) })
STATE_ECO = 'eco' NEST_MODE_HEAT_COOL = 'heat-cool'
STATE_HEAT_COOL = 'heat-cool'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH | SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
@ -118,14 +117,14 @@ class NestThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]: if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
return self._mode return self._mode
elif self._mode == STATE_HEAT_COOL: elif self._mode == NEST_MODE_HEAT_COOL:
return STATE_AUTO return STATE_AUTO
return STATE_UNKNOWN return STATE_UNKNOWN
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
if self._mode != STATE_HEAT_COOL and not self.is_away_mode_on: if self._mode != NEST_MODE_HEAT_COOL and not self.is_away_mode_on:
return self._target_temperature return self._target_temperature
return None return None
@ -136,7 +135,7 @@ class NestThermostat(ClimateDevice):
self._eco_temperature[0]: self._eco_temperature[0]:
# eco_temperature is always a low, high tuple # eco_temperature is always a low, high tuple
return self._eco_temperature[0] return self._eco_temperature[0]
if self._mode == STATE_HEAT_COOL: if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[0] return self._target_temperature[0]
return None return None
@ -147,7 +146,7 @@ class NestThermostat(ClimateDevice):
self._eco_temperature[1]: self._eco_temperature[1]:
# eco_temperature is always a low, high tuple # eco_temperature is always a low, high tuple
return self._eco_temperature[1] return self._eco_temperature[1]
if self._mode == STATE_HEAT_COOL: if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[1] return self._target_temperature[1]
return None return None
@ -160,7 +159,7 @@ class NestThermostat(ClimateDevice):
"""Set new target temperature.""" """Set new target temperature."""
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if self._mode == STATE_HEAT_COOL: if self._mode == NEST_MODE_HEAT_COOL:
if target_temp_low is not None and target_temp_high is not None: if target_temp_low is not None and target_temp_high is not None:
temp = (target_temp_low, target_temp_high) temp = (target_temp_low, target_temp_high)
else: else:
@ -173,7 +172,7 @@ class NestThermostat(ClimateDevice):
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]: if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
device_mode = operation_mode device_mode = operation_mode
elif operation_mode == STATE_AUTO: elif operation_mode == STATE_AUTO:
device_mode = STATE_HEAT_COOL device_mode = NEST_MODE_HEAT_COOL
self.device.mode = device_mode self.device.mode = device_mode
@property @property

View File

@ -294,14 +294,14 @@ class SensiboClimate(ClimateDevice):
self._id, 'swing', swing_mode, self._ac_states) self._id, 'swing', swing_mode, self._ac_states)
@asyncio.coroutine @asyncio.coroutine
def async_on(self): def async_turn_on(self):
"""Turn Sensibo unit on.""" """Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT): with async_timeout.timeout(TIMEOUT):
yield from self._client.async_set_ac_state_property( yield from self._client.async_set_ac_state_property(
self._id, 'on', True, self._ac_states) self._id, 'on', True, self._ac_states)
@asyncio.coroutine @asyncio.coroutine
def async_off(self): def async_turn_off(self):
"""Turn Sensibo unit on.""" """Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT): with async_timeout.timeout(TIMEOUT):
yield from self._client.async_set_ac_state_property( yield from self._client.async_set_ac_state_property(

View File

@ -6,13 +6,13 @@ https://home-assistant.io/components/climate.tesla/
""" """
import logging import logging
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE, ENTITY_ID_FORMAT, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE) ClimateDevice)
from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN
from homeassistant.components.tesla import TeslaDevice
from homeassistant.const import ( from homeassistant.const import (
TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE) ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -60,7 +60,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
return OPERATION_LIST return OPERATION_LIST
def update(self): def update(self):
"""Called by the Tesla device callback to update state.""" """Call by the Tesla device callback to update state."""
_LOGGER.debug("Updating: %s", self._name) _LOGGER.debug("Updating: %s", self._name)
self.tesla_device.update() self.tesla_device.update()
self._target_temperature = self.tesla_device.get_goal_temp() self._target_temperature = self.tesla_device.get_goal_temp()

View File

@ -7,25 +7,25 @@ Eneco.
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/climate.toon/ https://home-assistant.io/components/climate.toon/
""" """
import homeassistant.components.toon as toon_main
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, ATTR_TEMPERATURE, STATE_PERFORMANCE, STATE_HEAT, STATE_ECO, ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_PERFORMANCE,
STATE_COOL, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
import homeassistant.components.toon as toon_main
from homeassistant.const import TEMP_CELSIUS from homeassistant.const import TEMP_CELSIUS
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Toon thermostat.""" """Set up the Toon climate device."""
add_devices([ThermostatDevice(hass)], True) add_devices([ThermostatDevice(hass)], True)
class ThermostatDevice(ClimateDevice): class ThermostatDevice(ClimateDevice):
"""Interface class for the toon module and HA.""" """Representation of a Toon climate device."""
def __init__(self, hass): def __init__(self, hass):
"""Initialize the device.""" """Initialize the Toon climate device."""
self._name = 'Toon van Eneco' self._name = 'Toon van Eneco'
self.hass = hass self.hass = hass
self.thermos = hass.data[toon_main.TOON_HANDLE] self.thermos = hass.data[toon_main.TOON_HANDLE]
@ -47,12 +47,12 @@ class ThermostatDevice(ClimateDevice):
@property @property
def name(self): def name(self):
"""Name of this Thermostat.""" """Return the name of this thermostat."""
return self._name return self._name
@property @property
def temperature_unit(self): def temperature_unit(self):
"""The unit of measurement used by the platform.""" """Return the unit of measurement used by the platform."""
return TEMP_CELSIUS return TEMP_CELSIUS
@property @property
@ -63,7 +63,7 @@ class ThermostatDevice(ClimateDevice):
@property @property
def operation_list(self): def operation_list(self):
"""List of available operation modes.""" """Return a list of available operation modes."""
return self._operation_list return self._operation_list
@property @property
@ -82,7 +82,7 @@ class ThermostatDevice(ClimateDevice):
self.thermos.set_temp(temp) self.thermos.set_temp(temp)
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set new operation mode as toonlib requires it.""" """Set new operation mode."""
toonlib_values = { toonlib_values = {
STATE_PERFORMANCE: 'Comfort', STATE_PERFORMANCE: 'Comfort',
STATE_HEAT: 'Home', STATE_HEAT: 'Home',

View File

@ -0,0 +1,267 @@
"""
Support for Venstar WiFi Thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.venstar/
"""
import logging
import voluptuous as vol
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS,
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['venstarcolortouch==0.5']
_LOGGER = logging.getLogger(__name__)
ATTR_FAN_STATE = 'fan_state'
ATTR_HVAC_STATE = 'hvac_state'
DEFAULT_SSL = False
VALID_FAN_STATES = [STATE_ON, STATE_AUTO]
VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_TIMEOUT, default=5):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_USERNAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Venstar thermostat."""
import venstarcolortouch
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
timeout = config.get(CONF_TIMEOUT)
if config.get(CONF_SSL):
proto = 'https'
else:
proto = 'http'
client = venstarcolortouch.VenstarColorTouch(
addr=host, timeout=timeout, user=username, password=password,
proto=proto)
add_devices([VenstarThermostat(client)], True)
class VenstarThermostat(ClimateDevice):
"""Representation of a Venstar thermostat."""
def __init__(self, client):
"""Initialize the thermostat."""
self._client = client
def update(self):
"""Update the data from the thermostat."""
info_success = self._client.update_info()
sensor_success = self._client.update_sensors()
if not info_success or not sensor_success:
_LOGGER.error("Failed to update data")
@property
def supported_features(self):
"""Return the list of supported features."""
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE)
if self._client.mode == self._client.MODE_AUTO:
features |= (SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
if self._client.hum_active == 1:
features |= SUPPORT_TARGET_HUMIDITY
return features
@property
def name(self):
"""Return the name of the thermostat."""
return self._client.name
@property
def precision(self):
"""Return the precision of the system.
Venstar temperature values are passed back and forth in the
API as whole degrees C or F.
"""
return PRECISION_WHOLE
@property
def temperature_unit(self):
"""Return the unit of measurement, as defined by the API."""
if self._client.tempunits == self._client.TEMPUNITS_F:
return TEMP_FAHRENHEIT
else:
return TEMP_CELSIUS
@property
def fan_list(self):
"""Return the list of available fan modes."""
return VALID_FAN_STATES
@property
def operation_list(self):
"""Return the list of available operation modes."""
return VALID_THERMOSTAT_MODES
@property
def current_temperature(self):
"""Return the current temperature."""
return self._client.get_indoor_temp()
@property
def current_humidity(self):
"""Return the current humidity."""
return self._client.get_indoor_humidity()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self._client.mode == self._client.MODE_HEAT:
return STATE_HEAT
elif self._client.mode == self._client.MODE_COOL:
return STATE_COOL
elif self._client.mode == self._client.MODE_AUTO:
return STATE_AUTO
else:
return STATE_OFF
@property
def current_fan_mode(self):
"""Return the fan setting."""
if self._client.fan == self._client.FAN_AUTO:
return STATE_AUTO
else:
return STATE_ON
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
return {
ATTR_FAN_STATE: self._client.fanstate,
ATTR_HVAC_STATE: self._client.state,
}
@property
def target_temperature(self):
"""Return the target temperature we try to reach."""
if self._client.mode == self._client.MODE_HEAT:
return self._client.heattemp
elif self._client.mode == self._client.MODE_COOL:
return self._client.cooltemp
else:
return None
@property
def target_temperature_low(self):
"""Return the lower bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO:
return self._client.heattemp
else:
return None
@property
def target_temperature_high(self):
"""Return the upper bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO:
return self._client.cooltemp
else:
return None
@property
def target_humidity(self):
"""Return the humidity we try to reach."""
return self._client.hum_setpoint
@property
def min_humidity(self):
"""Return the minimum humidity. Hardcoded to 0 in API."""
return 0
@property
def max_humidity(self):
"""Return the maximum humidity. Hardcoded to 60 in API."""
return 60
def _set_operation_mode(self, operation_mode):
"""Change the operation mode (internal)."""
if operation_mode == STATE_HEAT:
success = self._client.set_mode(self._client.MODE_HEAT)
elif operation_mode == STATE_COOL:
success = self._client.set_mode(self._client.MODE_COOL)
elif operation_mode == STATE_AUTO:
success = self._client.set_mode(self._client.MODE_AUTO)
else:
success = self._client.set_mode(self._client.MODE_OFF)
if not success:
_LOGGER.error("Failed to change the operation mode")
return success
def set_temperature(self, **kwargs):
"""Set a new target temperature."""
set_temp = True
operation_mode = kwargs.get(ATTR_OPERATION_MODE, self._client.mode)
temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temperature = kwargs.get(ATTR_TEMPERATURE)
if operation_mode != self._client.mode:
set_temp = self._set_operation_mode(operation_mode)
if set_temp:
if operation_mode == self._client.MODE_HEAT:
success = self._client.set_setpoints(
temperature, self._client.cooltemp)
elif operation_mode == self._client.MODE_COOL:
success = self._client.set_setpoints(
self._client.heattemp, temperature)
elif operation_mode == self._client.MODE_AUTO:
success = self._client.set_setpoints(temp_low, temp_high)
else:
_LOGGER.error("The thermostat is currently not in a mode "
"that supports target temperature")
if not success:
_LOGGER.error("Failed to change the temperature")
def set_fan_mode(self, fan):
"""Set new target fan mode."""
if fan == STATE_ON:
success = self._client.set_fan(self._client.FAN_ON)
else:
success = self._client.set_fan(self._client.FAN_AUTO)
if not success:
_LOGGER.error("Failed to change the fan mode")
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
self._set_operation_mode(operation_mode)
def set_humidity(self, humidity):
"""Set new target humidity."""
success = self._client.set_hum_setpoint(humidity)
if not success:
_LOGGER.error("Failed to change the target humidity level")

View File

@ -8,16 +8,16 @@ import asyncio
import logging import logging
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC, ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND, ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_ELECTRIC,
STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY, STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, STATE_HIGH_DEMAND,
ATTR_TARGET_TEMP_HIGH, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, STATE_PERFORMANCE, SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, ClimateDevice)
SUPPORT_AUX_HEAT)
from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS) PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN, TEMP_CELSIUS)
from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.temperature import display_temp as show_temp
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,6 +30,8 @@ ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
ATTR_SMART_TEMPERATURE = 'smart_temperature' ATTR_SMART_TEMPERATURE = 'smart_temperature'
ATTR_TOTAL_CONSUMPTION = 'total_consumption' ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_VACATION_MODE = 'vacation_mode' ATTR_VACATION_MODE = 'vacation_mode'
ATTR_HEAT_ON = 'heat_on'
ATTR_COOL_ON = 'cool_on'
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
@ -93,7 +95,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Callback when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['climate'].append(self) self.hass.data[DOMAIN]['entities']['climate'].append(self)
@property @property
@ -104,7 +106,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional device state attributes."""
data = {} data = {}
target_temp_high = self.target_temperature_high target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low target_temp_low = self.target_temperature_low
@ -131,6 +133,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
if self.eco_target: if self.eco_target:
data[ATTR_ECO_TARGET] = self.eco_target data[ATTR_ECO_TARGET] = self.eco_target
if self.heat_on:
data[ATTR_HEAT_ON] = self.heat_on
if self.cool_on:
data[ATTR_COOL_ON] = self.cool_on
current_humidity = self.current_humidity current_humidity = self.current_humidity
if current_humidity is not None: if current_humidity is not None:
data[ATTR_CURRENT_HUMIDITY] = current_humidity data[ATTR_CURRENT_HUMIDITY] = current_humidity
@ -174,6 +182,16 @@ class WinkThermostat(WinkDevice, ClimateDevice):
"""Return status of if the thermostat has detected occupancy.""" """Return status of if the thermostat has detected occupancy."""
return self.wink.occupied() return self.wink.occupied()
@property
def heat_on(self):
"""Return whether or not the heat is actually heating."""
return self.wink.heat_on()
@property
def cool_on(self):
"""Return whether or not the heat is actually heating."""
return self.wink.heat_on()
@property @property
def current_operation(self): def current_operation(self):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
@ -385,7 +403,7 @@ class WinkAC(WinkDevice, ClimateDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional device state attributes."""
data = {} data = {}
target_temp_high = self.target_temperature_high target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low target_temp_low = self.target_temperature_low
@ -508,7 +526,7 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional device state attributes."""
data = {} data = {}
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled() data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data[ATTR_RHEEM_TYPE] = self.wink.rheem_type() data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()

View File

@ -1,4 +1,9 @@
"""Component to integrate the Home Assistant cloud.""" """
Component to integrate the Home Assistant cloud.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/cloud/
"""
import asyncio import asyncio
from datetime import datetime from datetime import datetime
import json import json
@ -26,18 +31,18 @@ REQUIREMENTS = ['warrant==0.6.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ALEXA = 'alexa' CONF_ALEXA = 'alexa'
CONF_GOOGLE_ACTIONS = 'google_actions' CONF_ALIASES = 'aliases'
CONF_FILTER = 'filter'
CONF_COGNITO_CLIENT_ID = 'cognito_client_id' CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_FILTER = 'filter'
CONF_GOOGLE_ACTIONS = 'google_actions'
CONF_RELAYER = 'relayer' CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id' CONF_USER_POOL_ID = 'user_pool_id'
CONF_ALIASES = 'aliases'
MODE_DEV = 'development'
DEFAULT_MODE = 'production' DEFAULT_MODE = 'production'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
CONF_ENTITY_CONFIG = 'entity_config' MODE_DEV = 'development'
ALEXA_ENTITY_SCHEMA = vol.Schema({ ALEXA_ENTITY_SCHEMA = vol.Schema({
vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string, vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string,
@ -149,7 +154,7 @@ class Cloud:
@property @property
def subscription_expired(self): def subscription_expired(self):
"""Return a boolen if the subscription has expired.""" """Return a boolean if the subscription has expired."""
return dt_util.utcnow() > self.expiration_date return dt_util.utcnow() > self.expiration_date
@property @property
@ -195,8 +200,8 @@ class Cloud:
if not jwt_success: if not jwt_success:
return False return False
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, self.hass.bus.async_listen_once(
self._start_cloud) EVENT_HOMEASSISTANT_START, self._start_cloud)
return True return True
@ -248,7 +253,7 @@ class Cloud:
for token in 'id_token', 'access_token': for token in 'id_token', 'access_token':
self._decode_claims(info[token]) self._decode_claims(info[token])
except ValueError as err: # Raised when token is invalid except ValueError as err: # Raised when token is invalid
_LOGGER.warning('Found invalid token %s: %s', token, err) _LOGGER.warning("Found invalid token %s: %s", token, err)
return return
self.id_token = info['id_token'] self.id_token = info['id_token']
@ -282,15 +287,15 @@ class Cloud:
header = jwt.get_unverified_header(token) header = jwt.get_unverified_header(token)
except jose_exceptions.JWTError as err: except jose_exceptions.JWTError as err:
raise ValueError(str(err)) from None raise ValueError(str(err)) from None
kid = header.get("kid") kid = header.get('kid')
if kid is None: if kid is None:
raise ValueError('No kid in header') raise ValueError("No kid in header")
# Locate the key for this kid # Locate the key for this kid
key = None key = None
for key_dict in self.jwt_keyset["keys"]: for key_dict in self.jwt_keyset['keys']:
if key_dict["kid"] == kid: if key_dict['kid'] == kid:
key = key_dict key = key_dict
break break
if not key: if not key:

View File

@ -1,7 +1,6 @@
"""Package to communicate with the authentication API.""" """Package to communicate with the authentication API."""
import logging import logging
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -22,7 +21,7 @@ class UserNotConfirmed(CloudError):
class ExpiredCode(CloudError): class ExpiredCode(CloudError):
"""Raised when an expired code is encoutered.""" """Raised when an expired code is encountered."""
class InvalidCode(CloudError): class InvalidCode(CloudError):
@ -38,7 +37,7 @@ class PasswordChangeRequired(CloudError):
class UnknownError(CloudError): class UnknownError(CloudError):
"""Raised when an unknown error occurrs.""" """Raised when an unknown error occurs."""
AWS_EXCEPTIONS = { AWS_EXCEPTIONS = {
@ -98,7 +97,7 @@ def resend_email_confirm(cloud, email):
def forgot_password(cloud, email): def forgot_password(cloud, email):
"""Initiate forgotten password flow.""" """Initialize forgotten password flow."""
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
cognito = _cognito(cloud, username=email) cognito = _cognito(cloud, username=email)

View File

@ -3,8 +3,8 @@ import asyncio
from functools import wraps from functools import wraps
import logging import logging
import voluptuous as vol
import async_timeout import async_timeout
import voluptuous as vol
from homeassistant.components.http import ( from homeassistant.components.http import (
HomeAssistantView, RequestDataValidator) HomeAssistantView, RequestDataValidator)
@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass): def async_setup(hass):
"""Initialize the HTTP api.""" """Initialize the HTTP API."""
hass.http.register_view(CloudLoginView) hass.http.register_view(CloudLoginView)
hass.http.register_view(CloudLogoutView) hass.http.register_view(CloudLogoutView)
hass.http.register_view(CloudAccountView) hass.http.register_view(CloudAccountView)
@ -40,7 +40,7 @@ _CLOUD_ERRORS = {
def _handle_cloud_errors(handler): def _handle_cloud_errors(handler):
"""Helper method to handle auth errors.""" """Handle auth errors."""
@asyncio.coroutine @asyncio.coroutine
@wraps(handler) @wraps(handler)
def error_handler(view, request, *args, **kwargs): def error_handler(view, request, *args, **kwargs):

View File

@ -12,7 +12,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api from . import auth_api
from .const import MESSAGE_EXPIRATION from .const import MESSAGE_EXPIRATION
HANDLERS = Registry() HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -85,7 +84,7 @@ class CloudIoT:
}) })
self.tries = 0 self.tries = 0
_LOGGER.info('Connected') _LOGGER.info("Connected")
self.state = STATE_CONNECTED self.state = STATE_CONNECTED
while not client.closed: while not client.closed:
@ -107,7 +106,7 @@ class CloudIoT:
disconnect_warn = 'Received invalid JSON.' disconnect_warn = 'Received invalid JSON.'
break break
_LOGGER.debug('Received message: %s', msg) _LOGGER.debug("Received message: %s", msg)
response = { response = {
'msgid': msg['msgid'], 'msgid': msg['msgid'],
@ -126,14 +125,14 @@ class CloudIoT:
response['error'] = 'unknown-handler' response['error'] = 'unknown-handler'
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error handling message') _LOGGER.exception("Error handling message")
response['error'] = 'exception' response['error'] = 'exception'
_LOGGER.debug('Publishing message: %s', response) _LOGGER.debug("Publishing message: %s", response)
yield from client.send_json(response) yield from client.send_json(response)
except auth_api.CloudError: except auth_api.CloudError:
_LOGGER.warning('Unable to connect: Unable to refresh token.') _LOGGER.warning("Unable to connect: Unable to refresh token.")
except client_exceptions.WSServerHandshakeError as err: except client_exceptions.WSServerHandshakeError as err:
if err.code == 401: if err.code == 401:
@ -141,18 +140,18 @@ class CloudIoT:
self.close_requested = True self.close_requested = True
# Should we notify user? # Should we notify user?
else: else:
_LOGGER.warning('Unable to connect: %s', err) _LOGGER.warning("Unable to connect: %s", err)
except client_exceptions.ClientError as err: except client_exceptions.ClientError as err:
_LOGGER.warning('Unable to connect: %s', err) _LOGGER.warning("Unable to connect: %s", err)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
if not self.close_requested: if not self.close_requested:
_LOGGER.exception('Unexpected error') _LOGGER.exception("Unexpected error")
finally: finally:
if disconnect_warn is not None: if disconnect_warn is not None:
_LOGGER.warning('Connection closed: %s', disconnect_warn) _LOGGER.warning("Connection closed: %s", disconnect_warn)
if remove_hass_stop_listener is not None: if remove_hass_stop_listener is not None:
remove_hass_stop_listener() remove_hass_stop_listener()
@ -169,7 +168,7 @@ class CloudIoT:
self.tries += 1 self.tries += 1
try: try:
# Sleep 0, 5, 10, 15 up to 30 seconds between retries # Sleep 0, 5, 10, 15 ... up to 30 seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep( self.retry_task = hass.async_add_job(asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop)) min(30, (self.tries - 1) * 5), loop=hass.loop))
yield from self.retry_task yield from self.retry_task
@ -205,8 +204,8 @@ def async_handle_message(hass, cloud, handler_name, payload):
@asyncio.coroutine @asyncio.coroutine
def async_handle_alexa(hass, cloud, payload): def async_handle_alexa(hass, cloud, payload):
"""Handle an incoming IoT message for Alexa.""" """Handle an incoming IoT message for Alexa."""
result = yield from alexa.async_handle_message(hass, cloud.alexa_config, result = yield from alexa.async_handle_message(
payload) hass, cloud.alexa_config, payload)
return result return result
@ -214,8 +213,8 @@ def async_handle_alexa(hass, cloud, payload):
@asyncio.coroutine @asyncio.coroutine
def async_handle_google_actions(hass, cloud, payload): def async_handle_google_actions(hass, cloud, payload):
"""Handle an incoming IoT message for Google Actions.""" """Handle an incoming IoT message for Google Actions."""
result = yield from ga.async_handle_message(hass, cloud.gactions_config, result = yield from ga.async_handle_message(
payload) hass, cloud.gactions_config, payload)
return result return result
@ -227,9 +226,9 @@ def async_handle_cloud(hass, cloud, payload):
if action == 'logout': if action == 'logout':
yield from cloud.logout() yield from cloud.logout()
_LOGGER.error('You have been logged out from Home Assistant cloud: %s', _LOGGER.error("You have been logged out from Home Assistant cloud: %s",
payload['reason']) payload['reason'])
else: else:
_LOGGER.warning('Received unknown cloud action: %s', action) _LOGGER.warning("Received unknown cloud action: %s", action)
return None return None

View File

@ -8,11 +8,11 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN, EVENT_HOMEASSISTANT_STOP) CONF_HOST, CONF_NAME, CONF_PIN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import (discovery) from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import (dispatcher_send) import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pycomfoconnect==0.3'] REQUIREMENTS = ['pycomfoconnect==0.3']
@ -115,7 +115,7 @@ class ComfoConnectBridge(object):
self.comfoconnect.disconnect() self.comfoconnect.disconnect()
def sensor_callback(self, var, value): def sensor_callback(self, var, value):
"""Callback function for sensor updates.""" """Call function for sensor updates."""
_LOGGER.debug("Got value from bridge: %d = %d", var, value) _LOGGER.debug("Got value from bridge: %d = %d", var, value)
from pycomfoconnect import ( from pycomfoconnect import (

View File

@ -5,9 +5,11 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.homematic/ https://home-assistant.io/components/cover.homematic/
""" """
import logging import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice, ATTR_POSITION from homeassistant.components.cover import CoverDevice, ATTR_POSITION,\
ATTR_TILT_POSITION
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -69,3 +71,40 @@ class HMCover(HMDevice, CoverDevice):
"""Generate a data dictoinary (self._data) from metadata.""" """Generate a data dictoinary (self._data) from metadata."""
self._state = "LEVEL" self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN}) self._data.update({self._state: STATE_UNKNOWN})
if "LEVEL_2" in self._hmdevice.WRITENODE:
self._data.update(
{'LEVEL_2': STATE_UNKNOWN})
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt.
None is unknown, 0 is closed, 100 is fully open.
"""
if 'LEVEL_2' not in self._data:
return None
return int(self._data.get('LEVEL_2', 0) * 100)
def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if "LEVEL_2" in self._data and ATTR_TILT_POSITION in kwargs:
position = float(kwargs[ATTR_TILT_POSITION])
position = min(100, max(0, position))
level = position / 100.0
self._hmdevice.set_cover_tilt_position(level, self._channel)
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
if "LEVEL_2" in self._data:
self._hmdevice.open_slats()
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
if "LEVEL_2" in self._data:
self._hmdevice.close_slats()
def stop_cover_tilt(self, **kwargs):
"""Stop cover tilt."""
if "LEVEL_2" in self._data:
self.stop_cover(**kwargs)

View File

@ -5,17 +5,18 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.knx/ https://home-assistant.io/components/cover.knx/
""" """
import asyncio import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.helpers.event import async_track_utc_time_change
from homeassistant.components.cover import ( from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION, ATTR_TILT_POSITION, PLATFORM_SCHEMA, SUPPORT_CLOSE,
SUPPORT_SET_POSITION, SUPPORT_STOP, SUPPORT_SET_TILT_POSITION, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION,
ATTR_POSITION, ATTR_TILT_POSITION) SUPPORT_STOP, CoverDevice)
from homeassistant.core import callback from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_utc_time_change
CONF_MOVE_LONG_ADDRESS = 'move_long_address' CONF_MOVE_LONG_ADDRESS = 'move_long_address'
CONF_MOVE_SHORT_ADDRESS = 'move_short_address' CONF_MOVE_SHORT_ADDRESS = 'move_short_address'
@ -50,20 +51,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
discovery_info=None):
"""Set up cover(s) for KNX platform.""" """Set up cover(s) for KNX platform."""
if DATA_KNX not in hass.data \ if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
or not hass.data[DATA_KNX].initialized: return
return False
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:
async_add_devices_config(hass, config, async_add_devices) async_add_devices_config(hass, config, async_add_devices)
return True
@callback @callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices): def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@ -114,7 +111,7 @@ class KNXCover(CoverDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
@asyncio.coroutine @asyncio.coroutine
def after_update_callback(device): def after_update_callback(device):
"""Callback after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument # pylint: disable=unused-argument
yield from self.async_update_ha_state() yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)
@ -209,7 +206,7 @@ class KNXCover(CoverDevice):
@callback @callback
def auto_updater_hook(self, now): def auto_updater_hook(self, now):
"""Callback for autoupdater.""" """Call for the autoupdater."""
# pylint: disable=unused-argument # pylint: disable=unused-argument
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
if self.device.position_reached(): if self.device.position_reached():

View File

@ -0,0 +1,76 @@
"""
Support for Lutron shades.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.lutron/
"""
import logging
from homeassistant.components.cover import (
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION,
ATTR_POSITION)
from homeassistant.components.lutron import (
LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Lutron shades."""
devs = []
for (area_name, device) in hass.data[LUTRON_DEVICES]['cover']:
dev = LutronCover(area_name, device, hass.data[LUTRON_CONTROLLER])
devs.append(dev)
add_devices(devs, True)
return True
class LutronCover(LutronDevice, CoverDevice):
"""Representation of a Lutron shade."""
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._lutron_device.last_level() < 1
@property
def current_cover_position(self):
"""Return the current position of cover."""
return self._lutron_device.last_level()
def close_cover(self, **kwargs):
"""Close the cover."""
self._lutron_device.level = 0
def open_cover(self, **kwargs):
"""Open the cover."""
self._lutron_device.level = 100
def set_cover_position(self, **kwargs):
"""Move the shade to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
self._lutron_device.level = position
def update(self):
"""Call when forcing a refresh of the device."""
# Reading the property (rather than last_level()) fetchs value
level = self._lutron_device.level
_LOGGER.debug("Lutron ID: %d updated to %f",
self._lutron_device.id, level)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr['Lutron Integration ID'] = self._lutron_device.id
return attr

View File

@ -5,12 +5,12 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/ https://home-assistant.io/components/cover.mysensors/
""" """
from homeassistant.components import mysensors from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_OFF, STATE_ON
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for covers.""" """Set up the MySensors platform for covers."""
mysensors.setup_mysensors_platform( mysensors.setup_mysensors_platform(
hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices) hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices)

View File

@ -29,12 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the RFXtrx cover.""" """Set up the RFXtrx cover."""
import RFXtrx as rfxtrxmod import RFXtrx as rfxtrxmod
covers = rfxtrx.get_devices_from_config(config, RfxtrxCover) covers = rfxtrx.get_devices_from_config(config, RfxtrxCover)
add_devices_callback(covers) add_devices(covers)
def cover_update(event): def cover_update(event):
"""Handle cover updates from the RFXtrx gateway.""" """Handle cover updates from the RFXtrx gateway."""
@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
new_device = rfxtrx.get_new_device(event, config, RfxtrxCover) new_device = rfxtrx.get_new_device(event, config, RfxtrxCover)
if new_device: if new_device:
add_devices_callback([new_device]) add_devices([new_device])
rfxtrx.apply_received_command(event) rfxtrx.apply_received_command(event)
@ -59,7 +59,7 @@ class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice):
@property @property
def should_poll(self): def should_poll(self):
"""No polling available in RFXtrx cover.""" """Return the polling state. No polling available in RFXtrx cover."""
return False return False
@property @property

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/cover.tahoma/
import logging import logging
from datetime import timedelta from datetime import timedelta
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT from homeassistant.components.cover import CoverDevice
from homeassistant.components.tahoma import ( from homeassistant.components.tahoma import (
DOMAIN as TAHOMA_DOMAIN, TahomaDevice) DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
@ -30,11 +30,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class TahomaCover(TahomaDevice, CoverDevice): class TahomaCover(TahomaDevice, CoverDevice):
"""Representation a Tahoma Cover.""" """Representation a Tahoma Cover."""
def __init__(self, tahoma_device, controller):
"""Initialize the Tahoma device."""
super().__init__(tahoma_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.unique_id)
def update(self): def update(self):
"""Update method.""" """Update method."""
self.controller.get_states([self.tahoma_device]) self.controller.get_states([self.tahoma_device])
@ -46,12 +41,16 @@ class TahomaCover(TahomaDevice, CoverDevice):
0 is closed, 100 is fully open. 0 is closed, 100 is fully open.
""" """
position = 100 - self.tahoma_device.active_states['core:ClosureState'] try:
position = 100 - \
self.tahoma_device.active_states['core:ClosureState']
if position <= 5: if position <= 5:
return 0 return 0
if position >= 95: if position >= 95:
return 100 return 100
return position return position
except KeyError:
return None
def set_cover_position(self, position, **kwargs): def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
@ -63,6 +62,14 @@ class TahomaCover(TahomaDevice, CoverDevice):
if self.current_cover_position is not None: if self.current_cover_position is not None:
return self.current_cover_position == 0 return self.current_cover_position == 0
@property
def device_class(self):
"""Return the class of the device."""
if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent':
return 'window'
else:
return None
def open_cover(self, **kwargs): def open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
self.apply_action('open') self.apply_action('open')
@ -78,10 +85,3 @@ class TahomaCover(TahomaDevice, CoverDevice):
self.apply_action('setPosition', 'secured') self.apply_action('setPosition', 'secured')
else: else:
self.apply_action('stopIdentify') self.apply_action('stopIdentify')
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent':
return 'window'
else:
return None

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.wink/
""" """
import asyncio import asyncio
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN
from homeassistant.components.wink import WinkDevice, DOMAIN from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
@ -31,25 +31,28 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Callback when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['cover'].append(self) self.hass.data[DOMAIN]['entities']['cover'].append(self)
def close_cover(self, **kwargs): def close_cover(self, **kwargs):
"""Close the shade.""" """Close the cover."""
self.wink.set_state(0) self.wink.set_state(0)
def open_cover(self, **kwargs): def open_cover(self, **kwargs):
"""Open the shade.""" """Open the cover."""
self.wink.set_state(1) self.wink.set_state(1)
def set_cover_position(self, position, **kwargs): def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position.""" """Move the cover shutter to a specific position."""
self.wink.set_state(float(position)/100) self.wink.set_state(float(position)/100)
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return the current position of roller shutter.""" """Return the current position of cover shutter."""
if self.wink.state() is not None:
return int(self.wink.state()*100) return int(self.wink.state()*100)
else:
return STATE_UNKNOWN
@property @property
def is_closed(self): def is_closed(self):

View File

@ -59,7 +59,7 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)}) self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
def parse_data(self, data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if ATTR_CURTAIN_LEVEL in data: if ATTR_CURTAIN_LEVEL in data:
self._pos = int(data[ATTR_CURTAIN_LEVEL]) self._pos = int(data[ATTR_CURTAIN_LEVEL])

View File

@ -5,11 +5,12 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/datadog/ https://home-assistant.io/components/datadog/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_PREFIX, from homeassistant.const import (
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_LOGBOOK_ENTRY,
STATE_UNKNOWN) EVENT_STATE_CHANGED, STATE_UNKNOWN)
from homeassistant.helpers import state as state_helper from homeassistant.helpers import state as state_helper
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -36,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config): def setup(hass, config):
"""Setup the Datadog component.""" """Set up the Datadog component."""
from datadog import initialize, statsd from datadog import initialize, statsd
conf = config[DOMAIN] conf = config[DOMAIN]
@ -81,36 +82,19 @@ def setup(hass, config):
if isinstance(value, (float, int)): if isinstance(value, (float, int)):
attribute = "{}.{}".format(metric, key.replace(' ', '_')) attribute = "{}.{}".format(metric, key.replace(' ', '_'))
statsd.gauge( statsd.gauge(
attribute, attribute, value, sample_rate=sample_rate, tags=tags)
value,
sample_rate=sample_rate,
tags=tags
)
_LOGGER.debug( _LOGGER.debug(
'Sent metric %s: %s (tags: %s)', "Sent metric %s: %s (tags: %s)", attribute, value, tags)
attribute,
value,
tags
)
try: try:
value = state_helper.state_as_number(state) value = state_helper.state_as_number(state)
except ValueError: except ValueError:
_LOGGER.debug( _LOGGER.debug(
'Error sending %s: %s (tags: %s)', "Error sending %s: %s (tags: %s)", metric, state.state, tags)
metric,
state.state,
tags
)
return return
statsd.gauge( statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags)
metric,
value,
sample_rate=sample_rate,
tags=tags
)
_LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags) _LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags)

View File

@ -4,20 +4,20 @@ Support for deCONZ devices.
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/deconz/ https://home-assistant.io/components/deconz/
""" """
import asyncio import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.discovery import SERVICE_DECONZ
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.discovery import SERVICE_DECONZ
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==23'] REQUIREMENTS = ['pydeconz==25']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,8 +27,8 @@ CONFIG_FILE = 'deconz.conf'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_API_KEY): cv.string, vol.Optional(CONF_API_KEY): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=80): cv.port, vol.Optional(CONF_PORT, default=80): cv.port,
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -53,14 +53,14 @@ Unlock your deCONZ gateway to register with Home Assistant.
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
"""Setup services and configuration for deCONZ component.""" """Set up services and configuration for deCONZ component."""
result = False result = False
config_file = yield from hass.async_add_job( config_file = yield from hass.async_add_job(
load_json, hass.config.path(CONFIG_FILE)) load_json, hass.config.path(CONFIG_FILE))
@asyncio.coroutine @asyncio.coroutine
def async_deconz_discovered(service, discovery_info): def async_deconz_discovered(service, discovery_info):
"""Called when deCONZ gateway has been found.""" """Call when deCONZ gateway has been found."""
deconz_config = {} deconz_config = {}
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST) deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT) deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
@ -85,17 +85,18 @@ def async_setup(hass, config):
@asyncio.coroutine @asyncio.coroutine
def async_setup_deconz(hass, config, deconz_config): def async_setup_deconz(hass, config, deconz_config):
"""Setup deCONZ session. """Set up a deCONZ session.
Load config, group, light and sensor data for server information. Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ. Start websocket for push notification of state changes from deCONZ.
""" """
_LOGGER.debug("deCONZ config %s", deconz_config)
from pydeconz import DeconzSession from pydeconz import DeconzSession
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, websession, **deconz_config) deconz = DeconzSession(hass.loop, websession, **deconz_config)
result = yield from deconz.async_load_parameters() result = yield from deconz.async_load_parameters()
if result is False: if result is False:
_LOGGER.error("Failed to communicate with deCONZ.") _LOGGER.error("Failed to communicate with deCONZ")
return False return False
hass.data[DOMAIN] = deconz hass.data[DOMAIN] = deconz
@ -125,8 +126,7 @@ def async_setup_deconz(hass, config, deconz_config):
data = call.data.get(SERVICE_DATA) data = call.data.get(SERVICE_DATA)
yield from deconz.async_put_state(field, data) yield from deconz.async_put_state(field, data)
hass.services.async_register( hass.services.async_register(
DOMAIN, 'configure', async_configure, DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)
schema=SERVICE_SCHEMA)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz.close) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz.close)
return True return True
@ -146,9 +146,8 @@ def async_request_configuration(hass, config, deconz_config):
deconz_config[CONF_API_KEY] = api_key deconz_config[CONF_API_KEY] = api_key
result = yield from async_setup_deconz(hass, config, deconz_config) result = yield from async_setup_deconz(hass, config, deconz_config)
if result: if result:
yield from hass.async_add_job(save_json, yield from hass.async_add_job(
hass.config.path(CONFIG_FILE), save_json, hass.config.path(CONFIG_FILE), deconz_config)
deconz_config)
configurator.async_request_done(request_id) configurator.async_request_done(request_id)
return return
else: else:

View File

@ -25,9 +25,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_PUB_KEY = 'pub_key' CONF_PUB_KEY = 'pub_key'
CONF_SSH_KEY = 'ssh_key' CONF_SSH_KEY = 'ssh_key'
DEFAULT_SSH_PORT = 22 DEFAULT_SSH_PORT = 22
SECRET_GROUP = 'Password or SSH Key' SECRET_GROUP = 'Password or SSH Key'
PLATFORM_SCHEMA = vol.All( PLATFORM_SCHEMA = vol.All(
@ -118,20 +116,10 @@ class AsusWrtDeviceScanner(DeviceScanner):
self.port = config[CONF_PORT] self.port = config[CONF_PORT]
if self.protocol == 'ssh': if self.protocol == 'ssh':
if not (self.ssh_key or self.password):
_LOGGER.error("No password or private key specified")
self.success_init = False
return
self.connection = SshConnection( self.connection = SshConnection(
self.host, self.port, self.username, self.password, self.host, self.port, self.username, self.password,
self.ssh_key, self.mode == 'ap') self.ssh_key, self.mode == 'ap')
else: else:
if not self.password:
_LOGGER.error("No password specified")
self.success_init = False
return
self.connection = TelnetConnection( self.connection = TelnetConnection(
self.host, self.port, self.username, self.password, self.host, self.port, self.username, self.password,
self.mode == 'ap') self.mode == 'ap')
@ -177,11 +165,16 @@ class AsusWrtDeviceScanner(DeviceScanner):
""" """
devices = {} devices = {}
devices.update(self._get_wl()) devices.update(self._get_wl())
devices = self._get_arp(devices) devices.update(self._get_arp())
devices = self._get_neigh(devices) devices.update(self._get_neigh(devices))
if not self.mode == 'ap': if not self.mode == 'ap':
devices.update(self._get_leases(devices)) devices.update(self._get_leases(devices))
return devices
ret_devices = {}
for key in devices:
if devices[key].ip is not None:
ret_devices[key] = devices[key]
return ret_devices
def _get_wl(self): def _get_wl(self):
lines = self.connection.run_command(_WL_CMD) lines = self.connection.run_command(_WL_CMD)
@ -219,18 +212,13 @@ class AsusWrtDeviceScanner(DeviceScanner):
result = _parse_lines(lines, _IP_NEIGH_REGEX) result = _parse_lines(lines, _IP_NEIGH_REGEX)
devices = {} devices = {}
for device in result: for device in result:
if device['mac']: if device['mac'] is not None:
mac = device['mac'].upper() mac = device['mac'].upper()
devices[mac] = Device(mac, None, None) old_ip = cur_devices.get(mac, {}).ip or None
else: devices[mac] = Device(mac, device.get('ip', old_ip), None)
cur_devices = { return devices
k: v for k, v in
cur_devices.items() if v.ip != device['ip']
}
cur_devices.update(devices)
return cur_devices
def _get_arp(self, cur_devices): def _get_arp(self):
lines = self.connection.run_command(_ARP_CMD) lines = self.connection.run_command(_ARP_CMD)
if not lines: if not lines:
return {} return {}
@ -240,13 +228,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
if device['mac']: if device['mac']:
mac = device['mac'].upper() mac = device['mac'].upper()
devices[mac] = Device(mac, device['ip'], None) devices[mac] = Device(mac, device['ip'], None)
else: return devices
cur_devices = {
k: v for k, v in
cur_devices.items() if v.ip != device['ip']
}
cur_devices.update(devices)
return cur_devices
class _Connection: class _Connection:
@ -272,7 +254,7 @@ class SshConnection(_Connection):
def __init__(self, host, port, username, password, ssh_key, ap): def __init__(self, host, port, username, password, ssh_key, ap):
"""Initialize the SSH connection properties.""" """Initialize the SSH connection properties."""
super(SshConnection, self).__init__() super().__init__()
self._ssh = None self._ssh = None
self._host = host self._host = host
@ -322,7 +304,7 @@ class SshConnection(_Connection):
self._ssh.login(self._host, self._username, self._ssh.login(self._host, self._username,
password=self._password, port=self._port) password=self._password, port=self._port)
super(SshConnection, self).connect() super().connect()
def disconnect(self): \ def disconnect(self): \
# pylint: disable=broad-except # pylint: disable=broad-except
@ -334,7 +316,7 @@ class SshConnection(_Connection):
finally: finally:
self._ssh = None self._ssh = None
super(SshConnection, self).disconnect() super().disconnect()
class TelnetConnection(_Connection): class TelnetConnection(_Connection):
@ -342,7 +324,7 @@ class TelnetConnection(_Connection):
def __init__(self, host, port, username, password, ap): def __init__(self, host, port, username, password, ap):
"""Initialize the Telnet connection properties.""" """Initialize the Telnet connection properties."""
super(TelnetConnection, self).__init__() super().__init__()
self._telnet = None self._telnet = None
self._host = host self._host = host
@ -361,7 +343,6 @@ class TelnetConnection(_Connection):
try: try:
if not self.connected: if not self.connected:
self.connect() self.connect()
self._telnet.write('{}\n'.format(command).encode('ascii')) self._telnet.write('{}\n'.format(command).encode('ascii'))
data = (self._telnet.read_until(self._prompt_string). data = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1]) split(b'\n')[1:-1])
@ -392,7 +373,7 @@ class TelnetConnection(_Connection):
self._telnet.write((self._password + '\n').encode('ascii')) self._telnet.write((self._password + '\n').encode('ascii'))
self._prompt_string = self._telnet.read_until(b'#').split(b'\n')[-1] self._prompt_string = self._telnet.read_until(b'#').split(b'\n')[-1]
super(TelnetConnection, self).connect() super().connect()
def disconnect(self): \ def disconnect(self): \
# pylint: disable=broad-except # pylint: disable=broad-except
@ -402,4 +383,4 @@ class TelnetConnection(_Connection):
except Exception: except Exception:
pass pass
super(TelnetConnection, self).disconnect() super().disconnect()

View File

@ -14,8 +14,8 @@ from aiohttp import web
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_HOST_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY) ATTR_MAC, PLATFORM_SCHEMA)
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback from homeassistant.core import callback
@ -24,35 +24,33 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.6.4'] REQUIREMENTS = ['aioautomatic==0.6.4']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
CONF_SECRET = 'secret'
CONF_DEVICES = 'devices'
CONF_CURRENT_LOCATION = 'current_location'
DEFAULT_TIMEOUT = 5
DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile']
FULL_SCOPE = DEFAULT_SCOPE + ['current_location']
ATTR_FUEL_LEVEL = 'fuel_level' ATTR_FUEL_LEVEL = 'fuel_level'
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json' AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json'
CONF_CLIENT_ID = 'client_id'
CONF_CURRENT_LOCATION = 'current_location'
CONF_DEVICES = 'devices'
CONF_SECRET = 'secret'
DATA_CONFIGURING = 'automatic_configurator_clients' DATA_CONFIGURING = 'automatic_configurator_clients'
DATA_REFRESH_TOKEN = 'refresh_token' DATA_REFRESH_TOKEN = 'refresh_token'
DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile']
DEFAULT_TIMEOUT = 5
DEPENDENCIES = ['http']
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
FULL_SCOPE = DEFAULT_SCOPE + ['current_location']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string, vol.Required(CONF_SECRET): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None): vol.All( vol.Optional(CONF_DEVICES, default=None):
cv.ensure_list, [cv.string]) vol.All(cv.ensure_list, [cv.string]),
}) })
@ -142,7 +140,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
@asyncio.coroutine @asyncio.coroutine
def initialize_callback(code, state): def initialize_callback(code, state):
"""Callback after OAuth2 response is returned.""" """Call after OAuth2 response is returned."""
try: try:
session = yield from client.create_session_from_oauth_code( session = yield from client.create_session_from_oauth_code(
code, state) code, state)
@ -181,12 +179,12 @@ class AutomaticAuthCallbackView(HomeAssistantView):
return response return response
else: else:
_LOGGER.error( _LOGGER.error(
"Error authorizing Automatic. Invalid response returned.") "Error authorizing Automatic. Invalid response returned")
return response return response
if DATA_CONFIGURING not in hass.data or \ if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]: params['state'] not in hass.data[DATA_CONFIGURING]:
_LOGGER.error("Automatic configuration request not found.") _LOGGER.error("Automatic configuration request not found")
return response return response
code = params['code'] code = params['code']
@ -220,16 +218,15 @@ class AutomaticData(object):
@asyncio.coroutine @asyncio.coroutine
def handle_event(self, name, event): def handle_event(self, name, event):
"""Coroutine to update state for a realtime event.""" """Coroutine to update state for a real time event."""
import aioautomatic import aioautomatic
# Fire a hass event
self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data) self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data)
if event.vehicle.id not in self.vehicle_info: if event.vehicle.id not in self.vehicle_info:
# If vehicle hasn't been seen yet, request the detailed # If vehicle hasn't been seen yet, request the detailed
# info for this vehicle. # info for this vehicle.
_LOGGER.info("New vehicle found.") _LOGGER.info("New vehicle found")
try: try:
vehicle = yield from event.get_vehicle() vehicle = yield from event.get_vehicle()
except aioautomatic.exceptions.AutomaticError as err: except aioautomatic.exceptions.AutomaticError as err:
@ -240,7 +237,7 @@ class AutomaticData(object):
if event.created_at < self.vehicle_seen[event.vehicle.id]: if event.created_at < self.vehicle_seen[event.vehicle.id]:
# Skip events received out of order # Skip events received out of order
_LOGGER.debug("Skipping out of order event. Event Created %s. " _LOGGER.debug("Skipping out of order event. Event Created %s. "
"Last seen event: %s.", event.created_at, "Last seen event: %s", event.created_at,
self.vehicle_seen[event.vehicle.id]) self.vehicle_seen[event.vehicle.id])
return return
self.vehicle_seen[event.vehicle.id] = event.created_at self.vehicle_seen[event.vehicle.id] = event.created_at
@ -270,13 +267,13 @@ class AutomaticData(object):
self.ws_close_requested = False self.ws_close_requested = False
if self.ws_reconnect_handle is not None: if self.ws_reconnect_handle is not None:
_LOGGER.debug("Retrying websocket connection.") _LOGGER.debug("Retrying websocket connection")
try: try:
ws_loop_future = yield from self.client.ws_connect() ws_loop_future = yield from self.client.ws_connect()
except aioautomatic.exceptions.UnauthorizedClientError: except aioautomatic.exceptions.UnauthorizedClientError:
_LOGGER.error("Client unauthorized for websocket connection. " _LOGGER.error("Client unauthorized for websocket connection. "
"Ensure Websocket is selected in the Automatic " "Ensure Websocket is selected in the Automatic "
"developer application event delivery preferences.") "developer application event delivery preferences")
return return
except aioautomatic.exceptions.AutomaticError as err: except aioautomatic.exceptions.AutomaticError as err:
if self.ws_reconnect_handle is None: if self.ws_reconnect_handle is None:
@ -290,14 +287,14 @@ class AutomaticData(object):
self.ws_reconnect_handle() self.ws_reconnect_handle()
self.ws_reconnect_handle = None self.ws_reconnect_handle = None
_LOGGER.info("Websocket connected.") _LOGGER.info("Websocket connected")
try: try:
yield from ws_loop_future yield from ws_loop_future
except aioautomatic.exceptions.AutomaticError as err: except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err)) _LOGGER.error(str(err))
_LOGGER.info("Websocket closed.") _LOGGER.info("Websocket closed")
# If websocket was close was not requested, attempt to reconnect # If websocket was close was not requested, attempt to reconnect
if not self.ws_close_requested: if not self.ws_close_requested:

View File

@ -15,7 +15,10 @@ import voluptuous as vol
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components import zone as zone_comp from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE,
SOURCE_TYPE_GPS
)
from homeassistant.const import STATE_HOME from homeassistant.const import STATE_HOME
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.util import slugify, decorator from homeassistant.util import slugify, decorator
@ -140,6 +143,11 @@ def _parse_see_args(message, subscribe_topic):
kwargs['attributes']['tid'] = message['tid'] kwargs['attributes']['tid'] = message['tid']
if 'addr' in message: if 'addr' in message:
kwargs['attributes']['address'] = message['addr'] kwargs['attributes']['address'] = message['addr']
if 't' in message:
if message['t'] == 'c':
kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_GPS
if message['t'] == 'b':
kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_BLUETOOTH_LE
return dev_id, kwargs return dev_id, kwargs

View File

@ -14,7 +14,9 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL,
CONF_PASSWORD, CONF_USERNAME)
CONF_HTTP_ID = 'http_id' CONF_HTTP_ID = 'http_id'
@ -22,6 +24,10 @@ _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=-1): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=True): vol.Any(
cv.boolean, cv.isfile),
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_HTTP_ID): cv.string vol.Required(CONF_HTTP_ID): cv.string
@ -39,16 +45,23 @@ class TomatoDeviceScanner(DeviceScanner):
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner.""" """Initialize the scanner."""
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID] host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
port = config[CONF_PORT]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD] username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.ssl, self.verify_ssl = config[CONF_SSL], config[CONF_VERIFY_SSL]
if port == -1:
port = 80
if self.ssl:
port = 443
self.req = requests.Request( self.req = requests.Request(
'POST', 'http://{}/update.cgi'.format(host), 'POST', 'http{}://{}:{}/update.cgi'.format(
"s" if self.ssl else "", host, port
),
data={'_http_id': http_id, 'exec': 'devlist'}, data={'_http_id': http_id, 'exec': 'devlist'},
auth=requests.auth.HTTPBasicAuth(username, password)).prepare() auth=requests.auth.HTTPBasicAuth(username, password)).prepare()
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);") self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.logger = logging.getLogger("{}.{}".format(__name__, "Tomato"))
self.last_results = {"wldev": [], "dhcpd_lease": []} self.last_results = {"wldev": [], "dhcpd_lease": []}
self.success_init = self._update_tomato_info() self.success_init = self._update_tomato_info()
@ -74,10 +87,16 @@ class TomatoDeviceScanner(DeviceScanner):
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
self.logger.info("Scanning") _LOGGER.info("Scanning")
try: try:
if self.ssl:
response = requests.Session().send(self.req,
timeout=3,
verify=self.verify_ssl)
else:
response = requests.Session().send(self.req, timeout=3) response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the # Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values. # wldev and dhcpd_lease values.
if response.status_code == 200: if response.status_code == 200:
@ -92,7 +111,7 @@ class TomatoDeviceScanner(DeviceScanner):
elif response.status_code == 401: elif response.status_code == 401:
# Authentication error # Authentication error
self.logger.exception(( _LOGGER.exception((
"Failed to authenticate, " "Failed to authenticate, "
"please check your username and password")) "please check your username and password"))
return False return False
@ -100,17 +119,17 @@ class TomatoDeviceScanner(DeviceScanner):
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or # We get this if we could not connect to the router or
# an invalid http_id was supplied. # an invalid http_id was supplied.
self.logger.exception("Failed to connect to the router or " _LOGGER.exception("Failed to connect to the router or "
"invalid http_id supplied") "invalid http_id supplied")
return False return False
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
# We get this if we could not connect to the router or # We get this if we could not connect to the router or
# an invalid http_id was supplied. # an invalid http_id was supplied.
self.logger.exception("Connection to the router timed out") _LOGGER.exception("Connection to the router timed out")
return False return False
except ValueError: except ValueError:
# If JSON decoder could not parse the response. # If JSON decoder could not parse the response.
self.logger.exception("Failed to parse response from router") _LOGGER.exception("Failed to parse response from router")
return False return False

View File

@ -11,11 +11,11 @@ import re
import requests import requests
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,8 +30,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_DHCP_SOFTWARE, vol.Optional(CONF_DHCP_SOFTWARE, default=DEFAULT_DHCP_SOFTWARE):
default=DEFAULT_DHCP_SOFTWARE): vol.In(DHCP_SOFTWARES) vol.In(DHCP_SOFTWARES),
}) })
@ -49,14 +49,14 @@ def get_scanner(hass, config):
def _refresh_on_acccess_denied(func): def _refresh_on_acccess_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again.""" """If remove rebooted, it lost our session so rebuld one and try again."""
def decorator(self, *args, **kwargs): def decorator(self, *args, **kwargs):
"""Wrapper function to refresh session_id on PermissionError.""" """Wrap the function to refresh session_id on PermissionError."""
try: try:
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except PermissionError: except PermissionError:
_LOGGER.warning("Invalid session detected." + _LOGGER.warning("Invalid session detected." +
" Tryign to refresh session_id and re-run the rpc") " Trying to refresh session_id and re-run RPC")
self.session_id = _get_session_id(self.url, self.username, self.session_id = _get_session_id(
self.password) self.url, self.username, self.password)
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
@ -80,8 +80,8 @@ class UbusDeviceScanner(DeviceScanner):
self.last_results = {} self.last_results = {}
self.url = 'http://{}/ubus'.format(host) self.url = 'http://{}/ubus'.format(host)
self.session_id = _get_session_id(self.url, self.username, self.session_id = _get_session_id(
self.password) self.url, self.username, self.password)
self.hostapd = [] self.hostapd = []
self.mac2name = None self.mac2name = None
self.success_init = self.session_id is not None self.success_init = self.session_id is not None

View File

@ -98,11 +98,15 @@ class UnifiDeviceScanner(DeviceScanner):
self.connected = False self.connected = False
def _get_update(self): def _get_update(self):
from pexpect import pxssh from pexpect import pxssh, exceptions
try: try:
if not self.connected: if not self.connected:
self._connect() self._connect()
# If we still aren't connected at this point
# don't try to send anything to the AP.
if not self.connected:
return None
self.ssh.sendline(UNIFI_COMMAND) self.ssh.sendline(UNIFI_COMMAND)
self.ssh.prompt() self.ssh.prompt()
return self.ssh.before return self.ssh.before
@ -110,7 +114,7 @@ class UnifiDeviceScanner(DeviceScanner):
_LOGGER.error("Unexpected SSH error: %s", str(err)) _LOGGER.error("Unexpected SSH error: %s", str(err))
self._disconnect() self._disconnect()
return None return None
except AssertionError as err: except (AssertionError, exceptions.EOF) as err:
_LOGGER.error("Connection to AP unavailable: %s", str(err)) _LOGGER.error("Connection to AP unavailable: %s", str(err))
self._disconnect() self._disconnect()
return None return None

View File

@ -84,7 +84,7 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine @asyncio.coroutine
def async_get_device_name(self, device): def async_get_device_name(self, device):
"""The firmware doesn't save the name of the wireless device.""" """Get the device name (the name of the wireless device not used)."""
return None return None
@asyncio.coroutine @asyncio.coroutine

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==1.2.3'] REQUIREMENTS = ['netdisco==1.2.4']
DOMAIN = 'discovery' DOMAIN = 'discovery'
@ -53,6 +53,7 @@ SERVICE_HANDLERS = {
SERVICE_TELLDUSLIVE: ('tellduslive', None), SERVICE_TELLDUSLIVE: ('tellduslive', None),
SERVICE_HUE: ('hue', None), SERVICE_HUE: ('hue', None),
SERVICE_DECONZ: ('deconz', None), SERVICE_DECONZ: ('deconz', None),
SERVICE_DAIKIN: ('daikin', None),
'google_cast': ('media_player', 'cast'), 'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'), 'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'), 'plex_mediaserver': ('media_player', 'plex'),

View File

@ -16,7 +16,7 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle from homeassistant.util import Throttle
from homeassistant.util.json import save_json from homeassistant.util.json import save_json
REQUIREMENTS = ['python-ecobee-api==0.0.14'] REQUIREMENTS = ['python-ecobee-api==0.0.15']
_CONFIGURING = {} _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -130,8 +130,9 @@ class Config(object):
self.cached_states = {} self.cached_states = {}
if self.type == TYPE_ALEXA: if self.type == TYPE_ALEXA:
_LOGGER.warning("Alexa type is deprecated and will be removed in a" _LOGGER.warning(
" future version") 'Emulated Hue running in legacy mode because type has been '
'specified. More info at https://goo.gl/M6tgz8')
# Get the IP address that will be passed to the Echo during discovery # Get the IP address that will be passed to the Echo during discovery
self.host_ip_addr = conf.get(CONF_HOST_IP) self.host_ip_addr = conf.get(CONF_HOST_IP)

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