mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
commit
5bde72d490
17
.coveragerc
17
.coveragerc
@ -97,6 +97,9 @@ omit =
|
||||
homeassistant/components/homematic/__init__.py
|
||||
homeassistant/components/*/homematic.py
|
||||
|
||||
homeassistant/components/ihc/*
|
||||
homeassistant/components/*/ihc.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/iota.py
|
||||
homeassistant/components/*/iota.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
@ -145,6 +151,9 @@ omit =
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/mychevy.py
|
||||
homeassistant/components/*/mychevy.py
|
||||
|
||||
homeassistant/components/mysensors.py
|
||||
homeassistant/components/*/mysensors.py
|
||||
|
||||
@ -241,6 +250,9 @@ omit =
|
||||
homeassistant/components/volvooncall.py
|
||||
homeassistant/components/*/volvooncall.py
|
||||
|
||||
homeassistant/components/waterfurnace.py
|
||||
homeassistant/components/*/waterfurnace.py
|
||||
|
||||
homeassistant/components/*/webostv.py
|
||||
|
||||
homeassistant/components/wemo.py
|
||||
@ -304,6 +316,7 @@ omit =
|
||||
homeassistant/components/camera/ring.py
|
||||
homeassistant/components/camera/rpi_camera.py
|
||||
homeassistant/components/camera/synology.py
|
||||
homeassistant/components/camera/xeoma.py
|
||||
homeassistant/components/camera/yi.py
|
||||
homeassistant/components/climate/econet.py
|
||||
homeassistant/components/climate/ephember.py
|
||||
@ -318,6 +331,7 @@ omit =
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/climate/sensibo.py
|
||||
homeassistant/components/climate/touchline.py
|
||||
homeassistant/components/climate/venstar.py
|
||||
homeassistant/components/cover/garadget.py
|
||||
homeassistant/components/cover/homematic.py
|
||||
homeassistant/components/cover/knx.py
|
||||
@ -327,7 +341,6 @@ omit =
|
||||
homeassistant/components/cover/scsgate.py
|
||||
homeassistant/components/device_tracker/actiontec.py
|
||||
homeassistant/components/device_tracker/aruba.py
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
homeassistant/components/device_tracker/automatic.py
|
||||
homeassistant/components/device_tracker/bbox.py
|
||||
homeassistant/components/device_tracker/bluetooth_le_tracker.py
|
||||
@ -504,6 +517,7 @@ omit =
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/blockchain.py
|
||||
homeassistant/components/sensor/bme280.py
|
||||
homeassistant/components/sensor/bme680.py
|
||||
homeassistant/components/sensor/bom.py
|
||||
homeassistant/components/sensor/broadlink.py
|
||||
homeassistant/components/sensor/buienradar.py
|
||||
@ -606,6 +620,7 @@ omit =
|
||||
homeassistant/components/sensor/sytadin.py
|
||||
homeassistant/components/sensor/tank_utility.py
|
||||
homeassistant/components/sensor/ted5000.py
|
||||
homeassistant/components/sensor/teksavvy.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
homeassistant/components/sensor/tibber.py
|
||||
homeassistant/components/sensor/time_date.py
|
||||
|
13
.github/move.yml
vendored
Normal file
13
.github/move.yml
vendored
Normal 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
|
||||
|
@ -32,12 +32,15 @@ homeassistant/components/zone.py @home-assistant/core
|
||||
# To monitor non-pypi additions
|
||||
requirements_all.txt @andrey-git
|
||||
|
||||
# HomeAssistant developer Teams
|
||||
Dockerfile @home-assistant/docker
|
||||
virtualization/Docker/* @home-assistant/docker
|
||||
|
||||
homeassistant/components/zwave/* @home-assistant/z-wave
|
||||
homeassistant/components/*/zwave.py @home-assistant/z-wave
|
||||
|
||||
homeassistant/components/hassio.py @home-assistant/hassio
|
||||
|
||||
# Indiviudal components
|
||||
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
|
||||
homeassistant/components/camera/yi.py @bachya
|
||||
|
@ -1,9 +1,8 @@
|
||||
"""
|
||||
ADS Component.
|
||||
Support for Automation Device Specification (ADS).
|
||||
|
||||
For more details about this component, please refer to the documentation.
|
||||
https://home-assistant.io/components/ads/
|
||||
|
||||
"""
|
||||
import threading
|
||||
import struct
|
||||
@ -29,7 +28,6 @@ ADSTYPE_BOOL = 'bool'
|
||||
|
||||
DOMAIN = 'ads'
|
||||
|
||||
# config variable names
|
||||
CONF_ADS_VAR = 'adsvar'
|
||||
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
|
||||
CONF_ADS_TYPE = 'adstype'
|
||||
@ -47,10 +45,10 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
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_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
|
||||
conf = config[DOMAIN]
|
||||
|
||||
# get ads connection parameters from config
|
||||
net_id = conf.get(CONF_DEVICE)
|
||||
ip_address = conf.get(CONF_IP_ADDRESS)
|
||||
port = conf.get(CONF_PORT)
|
||||
|
||||
# create a new ads connection
|
||||
client = pyads.Connection(net_id, port, ip_address)
|
||||
|
||||
# add some constants to AdsHub
|
||||
AdsHub.ADS_TYPEMAP = {
|
||||
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
|
||||
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
|
||||
@ -81,16 +76,13 @@ def setup(hass, config):
|
||||
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT
|
||||
AdsHub.ADSError = pyads.ADSError
|
||||
|
||||
# connect to ads client and try to connect
|
||||
try:
|
||||
ads = AdsHub(client)
|
||||
except pyads.pyads.ADSError:
|
||||
_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
|
||||
|
||||
# add ads hub to hass data collection, listen to shutdown
|
||||
hass.data[DATA_ADS] = ads
|
||||
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown)
|
||||
|
||||
@ -107,43 +99,41 @@ def setup(hass, config):
|
||||
|
||||
hass.services.register(
|
||||
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
|
||||
|
||||
|
||||
# tuple to hold data needed for notification
|
||||
# Tuple to hold data needed for notification
|
||||
NotificationItem = namedtuple(
|
||||
'NotificationItem', 'hnotify huser name plc_datatype callback'
|
||||
)
|
||||
|
||||
|
||||
class AdsHub:
|
||||
"""Representation of a PyADS connection."""
|
||||
class AdsHub(object):
|
||||
"""Representation of an ADS connection."""
|
||||
|
||||
def __init__(self, ads_client):
|
||||
"""Initialize the ADS Hub."""
|
||||
"""Initialize the ADS hub."""
|
||||
self._client = ads_client
|
||||
self._client.open()
|
||||
|
||||
# all ADS devices are registered here
|
||||
# All ADS devices are registered here
|
||||
self._devices = []
|
||||
self._notification_items = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
"""Shutdown ADS connection."""
|
||||
_LOGGER.debug('Shutting down ADS')
|
||||
_LOGGER.debug("Shutting down ADS")
|
||||
for notification_item in self._notification_items.values():
|
||||
self._client.del_device_notification(
|
||||
notification_item.hnotify,
|
||||
notification_item.huser
|
||||
)
|
||||
_LOGGER.debug(
|
||||
'Deleting device notification %d, %d',
|
||||
notification_item.hnotify, notification_item.huser
|
||||
)
|
||||
"Deleting device notification %d, %d",
|
||||
notification_item.hnotify, notification_item.huser)
|
||||
self._client.close()
|
||||
|
||||
def register_device(self, device):
|
||||
@ -167,33 +157,30 @@ class AdsHub:
|
||||
|
||||
with self._lock:
|
||||
hnotify, huser = self._client.add_device_notification(
|
||||
name, attr, self._device_notification_callback
|
||||
)
|
||||
name, attr, self._device_notification_callback)
|
||||
hnotify = int(hnotify)
|
||||
|
||||
_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(
|
||||
hnotify, huser, name, plc_datatype, callback
|
||||
)
|
||||
hnotify, huser, name, plc_datatype, callback)
|
||||
|
||||
def _device_notification_callback(self, addr, notification, huser):
|
||||
"""Handle device notifications."""
|
||||
contents = notification.contents
|
||||
|
||||
hnotify = int(contents.hNotification)
|
||||
_LOGGER.debug('Received Notification %d', hnotify)
|
||||
_LOGGER.debug("Received notification %d", hnotify)
|
||||
data = contents.data
|
||||
|
||||
try:
|
||||
notification_item = self._notification_items[hnotify]
|
||||
except KeyError:
|
||||
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify)
|
||||
_LOGGER.debug("Unknown device notification handle: %d", hnotify)
|
||||
return
|
||||
|
||||
# parse data to desired datatype
|
||||
# Parse data to desired datatype
|
||||
if notification_item.plc_datatype == self.PLCTYPE_BOOL:
|
||||
value = bool(struct.unpack('<?', bytearray(data)[:1])[0])
|
||||
elif notification_item.plc_datatype == self.PLCTYPE_INT:
|
||||
@ -204,7 +191,6 @@ class AdsHub:
|
||||
value = struct.unpack('<H', bytearray(data)[:2])[0]
|
||||
else:
|
||||
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)
|
||||
|
@ -6,12 +6,12 @@ https://home-assistant.io/components/alarm_control_panel.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (
|
||||
AbodeDevice, DOMAIN as ABODE_DOMAIN, CONF_ATTRIBUTION)
|
||||
from homeassistant.components.alarm_control_panel import (AlarmControlPanel)
|
||||
from homeassistant.const import (ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
|
||||
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
||||
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
@ -21,7 +21,7 @@ ICON = 'mdi:security'
|
||||
|
||||
|
||||
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]
|
||||
|
||||
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
|
||||
@ -41,7 +41,7 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
"""Return the icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
@ -81,5 +81,5 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
'device_id': self._device.device_id,
|
||||
'battery_backup': self._device.battery,
|
||||
'cellular_backup': self._device.is_cellular
|
||||
'cellular_backup': self._device.is_cellular,
|
||||
}
|
||||
|
@ -10,12 +10,11 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
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 (
|
||||
ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -67,6 +66,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
|
||||
def _message_callback(self, message):
|
||||
"""Handle received messages."""
|
||||
if message.alarm_sounding or message.fire_alarm:
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif message.armed_away:
|
||||
@ -120,7 +120,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
'entry_delay_off': self._entry_delay_off,
|
||||
'programming_mode': self._programming_mode,
|
||||
'ready': self._ready,
|
||||
'zone_bypassed': self._zone_bypassed
|
||||
'zone_bypassed': self._zone_bypassed,
|
||||
}
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
|
@ -4,17 +4,18 @@ Interfaces with Alarm.com alarm control panels.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE,
|
||||
CONF_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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):
|
||||
"""Represent an Alarm.com status."""
|
||||
"""Representation of an Alarm.com status."""
|
||||
|
||||
def __init__(self, hass, name, code, username, password):
|
||||
"""Initialize the Alarm.com status."""
|
||||
@ -57,10 +58,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
self._password = password
|
||||
self._websession = async_get_clientsession(self._hass)
|
||||
self._state = STATE_UNKNOWN
|
||||
self._alarm = Alarmdotcom(username,
|
||||
password,
|
||||
self._websession,
|
||||
hass.loop)
|
||||
self._alarm = Alarmdotcom(
|
||||
username, password, self._websession, hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_login(self):
|
||||
@ -80,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
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 '.+'
|
||||
|
||||
@property
|
||||
@ -116,5 +115,5 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Wrong code entered.')
|
||||
_LOGGER.warning("Wrong code entered")
|
||||
return check
|
||||
|
@ -29,9 +29,9 @@ DEFAULT_PORT = 5007
|
||||
SCAN_INTERVAL = timedelta(seconds=1)
|
||||
|
||||
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_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)])
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
||||
return False
|
||||
return
|
||||
|
||||
|
||||
class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
@ -107,7 +107,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
newstate = STATE_ALARM_ARMED_AWAY
|
||||
|
||||
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
|
||||
return self._state
|
||||
|
||||
|
@ -10,13 +10,13 @@ import requests
|
||||
import voluptuous as vol
|
||||
|
||||
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.const import (
|
||||
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
|
||||
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED, EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
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']
|
||||
|
||||
@ -35,6 +35,7 @@ DEFAULT_REPORT_SERVER_PORT = 52010
|
||||
DEFAULT_VERSION = 'GATE-01'
|
||||
DOMAIN = 'egardia'
|
||||
D_EGARDIASRV = 'egardiaserver'
|
||||
|
||||
NOTIFICATION_ID = 'egardia_notification'
|
||||
NOTIFICATION_TITLE = 'Egardia'
|
||||
|
||||
@ -97,8 +98,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
server = egardiaserver.EgardiaServer('', rs_port)
|
||||
bound = server.bind()
|
||||
if not bound:
|
||||
raise IOError("Binding error occurred while " +
|
||||
"starting EgardiaServer")
|
||||
raise IOError(
|
||||
"Binding error occurred while starting EgardiaServer")
|
||||
hass.data[D_EGARDIASRV] = server
|
||||
server.start()
|
||||
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)
|
||||
|
||||
def handle_stop_event(event):
|
||||
"""Callback function for HA stop event."""
|
||||
"""Call function for Home Assistant stop event."""
|
||||
hass.data[D_EGARDIASRV].stop()
|
||||
|
||||
# listen to home assistant stop event
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
|
||||
|
||||
# add egardia alarm device
|
||||
add_devices([eg_dev], True)
|
||||
|
||||
|
||||
class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Egardia alarm."""
|
||||
|
||||
def __init__(self, name, egardiasystem,
|
||||
rs_enabled=False, rs_codes=None):
|
||||
"""Initialize object."""
|
||||
def __init__(self, name, egardiasystem, rs_enabled=False, rs_codes=None):
|
||||
"""Initialize the Egardia alarm."""
|
||||
self._name = name
|
||||
self._egardiasystem = egardiasystem
|
||||
self._status = None
|
||||
@ -149,7 +147,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
return False
|
||||
|
||||
def handle_status_event(self, event):
|
||||
"""Handle egardia_system_status_event."""
|
||||
"""Handle the Egardia system status event."""
|
||||
statuscode = event.get('status')
|
||||
if statuscode is not None:
|
||||
status = self.lookupstatusfromcode(statuscode)
|
||||
|
@ -8,12 +8,12 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, CONF_HOST, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, CONF_NAME)
|
||||
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyialarm==0.2']
|
||||
|
||||
@ -33,9 +33,9 @@ def no_application_protocol(value):
|
||||
|
||||
|
||||
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_USERNAME): cv.string,
|
||||
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
|
||||
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):
|
||||
"""Represent an iAlarm status."""
|
||||
"""Representation of an iAlarm status."""
|
||||
|
||||
def __init__(self, name, username, password, url):
|
||||
"""Initialize the iAlarm status."""
|
||||
|
@ -11,15 +11,17 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
|
||||
STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE,
|
||||
CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
|
||||
CONF_DISARM_AFTER_TRIGGER)
|
||||
CONF_CODE, CONF_DELAY_TIME, CONF_DISARM_AFTER_TRIGGER, CONF_NAME,
|
||||
CONF_PENDING_TIME, CONF_PLATFORM, CONF_TRIGGER_TIME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
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'
|
||||
|
||||
@ -44,6 +46,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
|
||||
|
||||
|
||||
def _state_validator(config):
|
||||
"""Validate the state."""
|
||||
config = copy.deepcopy(config)
|
||||
for state in SUPPORTED_PRETRIGGER_STATES:
|
||||
if CONF_DELAY_TIME not in config[state]:
|
||||
@ -58,6 +61,7 @@ def _state_validator(config):
|
||||
|
||||
|
||||
def _state_schema(state):
|
||||
"""Validate the state."""
|
||||
schema = {}
|
||||
if state in SUPPORTED_PRETRIGGER_STATES:
|
||||
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
|
||||
@ -97,8 +101,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All({
|
||||
_state_schema(STATE_ALARM_TRIGGERED),
|
||||
}, _state_validator))
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the manual alarm platform."""
|
||||
@ -151,7 +153,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the plling state."""
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
@ -182,23 +184,26 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def _active_state(self):
|
||||
"""Get the current state."""
|
||||
if self.state == STATE_ALARM_PENDING:
|
||||
return self._previous_state
|
||||
else:
|
||||
return self._state
|
||||
|
||||
def _pending_time(self, state):
|
||||
"""Get the pending time."""
|
||||
pending_time = self._pending_time_by_state[state]
|
||||
if state == STATE_ALARM_TRIGGERED:
|
||||
pending_time += self._delay_time_by_state[self._previous_state]
|
||||
return pending_time
|
||||
|
||||
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()
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters."""
|
||||
"""Return one or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
@ -250,6 +255,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._update_state(STATE_ALARM_TRIGGERED)
|
||||
|
||||
def _update_state(self, state):
|
||||
"""Update the state."""
|
||||
if self._state == state:
|
||||
return
|
||||
|
||||
|
@ -26,6 +26,8 @@ from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CODE_TEMPLATE = 'code_template'
|
||||
|
||||
CONF_PAYLOAD_DISARM = 'payload_disarm'
|
||||
@ -58,6 +60,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
|
||||
|
||||
|
||||
def _state_validator(config):
|
||||
"""Validate the state."""
|
||||
config = copy.deepcopy(config)
|
||||
for state in SUPPORTED_PRETRIGGER_STATES:
|
||||
if CONF_DELAY_TIME not in config[state]:
|
||||
@ -72,6 +75,7 @@ def _state_validator(config):
|
||||
|
||||
|
||||
def _state_schema(state):
|
||||
"""Validate the state."""
|
||||
schema = {}
|
||||
if state in SUPPORTED_PRETRIGGER_STATES:
|
||||
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,
|
||||
}), _state_validator))
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, name, code, code_template,
|
||||
disarm_after_trigger,
|
||||
state_topic, command_topic, qos,
|
||||
payload_disarm, payload_arm_home, payload_arm_away,
|
||||
payload_arm_night, config):
|
||||
def __init__(self, hass, name, code, code_template, disarm_after_trigger,
|
||||
state_topic, command_topic, qos, payload_disarm,
|
||||
payload_arm_home, payload_arm_away, payload_arm_night,
|
||||
config):
|
||||
"""Init the manual MQTT alarm panel."""
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._hass = hass
|
||||
@ -219,23 +220,26 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def _active_state(self):
|
||||
"""Get the current state."""
|
||||
if self.state == STATE_ALARM_PENDING:
|
||||
return self._previous_state
|
||||
else:
|
||||
return self._state
|
||||
|
||||
def _pending_time(self, state):
|
||||
"""Get the pending time."""
|
||||
pending_time = self._pending_time_by_state[state]
|
||||
if state == STATE_ALARM_TRIGGERED:
|
||||
pending_time += self._delay_time_by_state[self._previous_state]
|
||||
return pending_time
|
||||
|
||||
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()
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters."""
|
||||
"""Return one or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
@ -280,6 +284,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
self._update_state(STATE_ALARM_TRIGGERED)
|
||||
|
||||
def _update_state(self, state):
|
||||
"""Update the state."""
|
||||
if self._state == state:
|
||||
return
|
||||
|
||||
@ -329,7 +334,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
return state_attr
|
||||
|
||||
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.
|
||||
"""
|
||||
@ -358,5 +363,5 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
@asyncio.coroutine
|
||||
def _async_state_changed_listener(self, entity_id, old_state, new_state):
|
||||
"""Publish state change to MQTT."""
|
||||
mqtt.async_publish(self.hass, self._state_topic, new_state.state,
|
||||
self._qos, True)
|
||||
mqtt.async_publish(
|
||||
self.hass, self._state_topic, new_state.state, self._qos, True)
|
||||
|
@ -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_HOME, default=DEFAULT_ARM_HOME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
|
||||
})
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -12,8 +12,8 @@ import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT)
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pynx584==0.4']
|
||||
@ -25,14 +25,14 @@ DEFAULT_NAME = 'NX584'
|
||||
DEFAULT_PORT = 5007
|
||||
|
||||
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_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):
|
||||
"""Set up the nx584 platform."""
|
||||
"""Set up the NX584 platform."""
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
@ -88,7 +88,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
except IndexError:
|
||||
_LOGGER.error("nx584 reports no partitions")
|
||||
_LOGGER.error("NX584 reports no partitions")
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
|
||||
|
@ -8,9 +8,8 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.satel_integra import (CONF_ARM_HOME_MODE,
|
||||
DATA_SATEL,
|
||||
SIGNAL_PANEL_MESSAGE)
|
||||
from homeassistant.components.satel_integra import (
|
||||
CONF_ARM_HOME_MODE, DATA_SATEL, SIGNAL_PANEL_MESSAGE)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
@ -21,12 +20,12 @@ DEPENDENCIES = ['satel_integra']
|
||||
|
||||
@asyncio.coroutine
|
||||
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:
|
||||
return
|
||||
|
||||
device = SatelIntegraAlarmPanel("Alarm Panel",
|
||||
discovery_info.get(CONF_ARM_HOME_MODE))
|
||||
device = SatelIntegraAlarmPanel(
|
||||
"Alarm Panel", discovery_info.get(CONF_ARM_HOME_MODE))
|
||||
async_add_devices([device])
|
||||
|
||||
|
||||
@ -47,7 +46,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
|
||||
@callback
|
||||
def _message_callback(self, message):
|
||||
|
||||
"""Handle received messages."""
|
||||
if message != self._state:
|
||||
self._state = message
|
||||
self.async_schedule_update_ha_state()
|
||||
@ -90,5 +89,5 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(code,
|
||||
self._arm_home_mode)
|
||||
yield from self.hass.data[DATA_SATEL].arm(
|
||||
code, self._arm_home_mode)
|
||||
|
@ -11,9 +11,9 @@ import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.5']
|
||||
@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'SimpliSafe'
|
||||
DOMAIN = 'simplisafe'
|
||||
|
||||
NOTIFICATION_ID = 'simplisafe_notification'
|
||||
NOTIFICATION_TITLE = 'SimpliSafe Setup'
|
||||
|
||||
@ -65,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation a SimpliSafe alarm."""
|
||||
"""Representation of a SimpliSafe alarm."""
|
||||
|
||||
def __init__(self, simplisafe, name, code):
|
||||
"""Initialize the SimpliSafe alarm."""
|
||||
@ -82,7 +83,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
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 '.+'
|
||||
|
||||
@property
|
||||
@ -103,12 +104,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
'temperature': self.simplisafe.temperature(),
|
||||
'alarm': self.simplisafe.alarm(),
|
||||
'co': self.simplisafe.carbon_monoxide(),
|
||||
'fire': self.simplisafe.fire(),
|
||||
'alarm': self.simplisafe.alarm(),
|
||||
'flood': self.simplisafe.flood(),
|
||||
'last_event': self.simplisafe.last_event(),
|
||||
'flood': self.simplisafe.flood()
|
||||
'temperature': self.simplisafe.temperature(),
|
||||
}
|
||||
|
||||
def update(self):
|
||||
|
@ -9,26 +9,27 @@ import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
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 (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED,
|
||||
'1': STATE_ALARM_ARMED_HOME,
|
||||
'3': STATE_ALARM_ARMED_AWAY}
|
||||
SPC_AREA_MODE_TO_STATE = {
|
||||
'0': STATE_ALARM_DISARMED,
|
||||
'1': STATE_ALARM_ARMED_HOME,
|
||||
'3': STATE_ALARM_ARMED_AWAY,
|
||||
}
|
||||
|
||||
|
||||
def _get_alarm_state(spc_mode):
|
||||
"""Get the alarm state."""
|
||||
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
|
||||
|
||||
|
||||
@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 SPC alarm control panel platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_AREAS] is None):
|
||||
@ -42,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices,
|
||||
|
||||
|
||||
class SpcAlarm(alarm.AlarmControlPanel):
|
||||
"""Represents the SPC alarm panel."""
|
||||
"""Representation of the SPC alarm panel."""
|
||||
|
||||
def __init__(self, api, area):
|
||||
"""Initialize the SPC alarm panel."""
|
||||
@ -57,7 +58,7 @@ class SpcAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Calbback for init handlers."""
|
||||
"""Call for adding new entities."""
|
||||
self.hass.data[DATA_REGISTRY].register_alarm_device(
|
||||
self._area_id, self)
|
||||
|
||||
|
@ -8,8 +8,8 @@ import logging
|
||||
from time import sleep
|
||||
|
||||
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 (CONF_ALARM, CONF_CODE_DIGITS)
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
@ -43,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Verisure alarm status."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initalize the Verisure alarm panel."""
|
||||
"""Initialize the Verisure alarm panel."""
|
||||
self._state = STATE_UNKNOWN
|
||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
||||
self._changed_by = None
|
||||
|
@ -8,11 +8,10 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.const import (STATE_UNKNOWN,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY)
|
||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -41,7 +40,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
|
||||
@property
|
||||
|
@ -10,18 +10,33 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import entityfilter
|
||||
|
||||
from . import flash_briefings, intent, smart_home
|
||||
from .const import (
|
||||
DOMAIN, CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL)
|
||||
from . import flash_briefings, intent
|
||||
CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
|
||||
CONF_FILTER, CONF_ENTITY_CONFIG)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FLASH_BRIEFINGS = 'flash_briefings'
|
||||
CONF_SMART_HOME = 'smart_home'
|
||||
|
||||
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({
|
||||
DOMAIN: {
|
||||
@ -33,7 +48,10 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_TEXT, default=""): 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)
|
||||
|
||||
@ -49,4 +67,12 @@ def async_setup(hass, config):
|
||||
if 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
|
||||
|
@ -8,6 +8,9 @@ CONF_AUDIO = 'audio'
|
||||
CONF_TEXT = 'text'
|
||||
CONF_DISPLAY_URL = 'display_url'
|
||||
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_ENTITY_CONFIG = 'entity_config'
|
||||
|
||||
ATTR_UID = 'uid'
|
||||
ATTR_UPDATE_DATE = 'updateDate'
|
||||
ATTR_TITLE_TEXT = 'titleText'
|
||||
|
@ -5,19 +5,18 @@ For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import copy
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from homeassistant.components import http
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.components import http
|
||||
|
||||
from .const import (
|
||||
CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL, ATTR_UID,
|
||||
ATTR_UPDATE_DATE, ATTR_TITLE_TEXT, ATTR_STREAM_URL, ATTR_MAIN_TEXT,
|
||||
ATTR_REDIRECTION_URL, DATE_FORMAT)
|
||||
|
||||
ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT,
|
||||
ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT,
|
||||
CONF_TITLE, CONF_UID, DATE_FORMAT)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -46,11 +45,11 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
@callback
|
||||
def get(self, request, briefing_id):
|
||||
"""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)
|
||||
|
||||
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)
|
||||
return b'', 404
|
||||
|
||||
|
@ -3,30 +3,31 @@ Support for Alexa skill service end point.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import intent
|
||||
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 .const import DOMAIN, SYN_RESOLUTION_MATCH
|
||||
|
||||
INTENTS_API_ENDPOINT = '/api/alexa'
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HANDLERS = Registry()
|
||||
|
||||
INTENTS_API_ENDPOINT = '/api/alexa'
|
||||
|
||||
|
||||
class SpeechType(enum.Enum):
|
||||
"""The Alexa speech types."""
|
||||
|
||||
plaintext = "PlainText"
|
||||
ssml = "SSML"
|
||||
plaintext = 'PlainText'
|
||||
ssml = 'SSML'
|
||||
|
||||
|
||||
SPEECH_MAPPINGS = {
|
||||
@ -38,8 +39,8 @@ SPEECH_MAPPINGS = {
|
||||
class CardType(enum.Enum):
|
||||
"""The Alexa card types."""
|
||||
|
||||
simple = "Simple"
|
||||
link_account = "LinkAccount"
|
||||
simple = 'Simple'
|
||||
link_account = 'LinkAccount'
|
||||
|
||||
|
||||
@callback
|
||||
@ -64,7 +65,7 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
|
||||
_LOGGER.debug('Received Alexa request: %s', message)
|
||||
_LOGGER.debug("Received Alexa request: %s", message)
|
||||
|
||||
try:
|
||||
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."))
|
||||
|
||||
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(
|
||||
hass, message,
|
||||
"Invalid slot information received for this intent."))
|
||||
@ -109,6 +110,7 @@ def async_handle_message(hass, message):
|
||||
- intent.UnknownIntent
|
||||
- intent.InvalidSlotInfo
|
||||
- intent.IntentError
|
||||
|
||||
"""
|
||||
req = message.get('request')
|
||||
req_type = req['type']
|
||||
@ -138,6 +140,7 @@ def async_handle_intent(hass, message):
|
||||
- intent.UnknownIntent
|
||||
- intent.InvalidSlotInfo
|
||||
- intent.IntentError
|
||||
|
||||
"""
|
||||
req = message.get('request')
|
||||
alexa_intent_info = req.get('intent')
|
||||
|
@ -2,75 +2,374 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from datetime import datetime
|
||||
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.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
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_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET)
|
||||
from homeassistant.components import (
|
||||
alert, automation, cover, fan, group, input_boolean, light, lock,
|
||||
media_player, scene, script, switch)
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
|
||||
CONF_UNIT_OF_MEASUREMENT)
|
||||
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
|
||||
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
API_DIRECTIVE = 'directive'
|
||||
API_ENDPOINT = 'endpoint'
|
||||
API_EVENT = 'event'
|
||||
API_CONTEXT = 'context'
|
||||
API_HEADER = 'header'
|
||||
API_PAYLOAD = 'payload'
|
||||
|
||||
API_TEMP_UNITS = {
|
||||
TEMP_FAHRENHEIT: 'FAHRENHEIT',
|
||||
TEMP_CELSIUS: 'CELSIUS',
|
||||
}
|
||||
|
||||
SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home'
|
||||
|
||||
CONF_DESCRIPTION = 'description'
|
||||
CONF_DISPLAY_CATEGORIES = 'display_categories'
|
||||
CONF_NAME = 'name'
|
||||
|
||||
HANDLERS = Registry()
|
||||
|
||||
|
||||
MAPPING_COMPONENT = {
|
||||
alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
automation.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
cover.DOMAIN: [
|
||||
'DOOR', ('Alexa.PowerController',), {
|
||||
cover.SUPPORT_SET_POSITION: 'Alexa.PercentageController',
|
||||
}
|
||||
],
|
||||
fan.DOMAIN: [
|
||||
'OTHER', ('Alexa.PowerController',), {
|
||||
fan.SUPPORT_SET_SPEED: 'Alexa.PercentageController',
|
||||
}
|
||||
],
|
||||
group.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
input_boolean.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
light.DOMAIN: [
|
||||
'LIGHT', ('Alexa.PowerController',), {
|
||||
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController',
|
||||
light.SUPPORT_RGB_COLOR: 'Alexa.ColorController',
|
||||
light.SUPPORT_XY_COLOR: 'Alexa.ColorController',
|
||||
light.SUPPORT_COLOR_TEMP: 'Alexa.ColorTemperatureController',
|
||||
}
|
||||
],
|
||||
lock.DOMAIN: ['SMARTLOCK', ('Alexa.LockController',), None],
|
||||
media_player.DOMAIN: [
|
||||
'TV', ('Alexa.PowerController',), {
|
||||
media_player.SUPPORT_VOLUME_SET: 'Alexa.Speaker',
|
||||
media_player.SUPPORT_PLAY: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_PAUSE: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_STOP: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_NEXT_TRACK: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_PREVIOUS_TRACK: 'Alexa.PlaybackController',
|
||||
}
|
||||
],
|
||||
scene.DOMAIN: ['ACTIVITY_TRIGGER', ('Alexa.SceneController',), None],
|
||||
script.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
|
||||
class _DisplayCategory(object):
|
||||
"""Possible display categories for Discovery response.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
|
||||
"""
|
||||
|
||||
# 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,
|
||||
}
|
||||
|
||||
if supports_deactivation is not None:
|
||||
result['supportsDeactivation'] = supports_deactivation
|
||||
|
||||
if retrievable is not None:
|
||||
result['retrievable'] = retrievable
|
||||
|
||||
if properties_supported is not None:
|
||||
result['properties'] = {'supported': properties_supported}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _EntityCapabilities(object):
|
||||
def __init__(self, config, entity):
|
||||
self.config = config
|
||||
self.entity = entity
|
||||
|
||||
def display_categories(self):
|
||||
"""Return a list of display categories."""
|
||||
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
|
||||
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
||||
return [entity_conf[CONF_DISPLAY_CATEGORIES]]
|
||||
return self.default_display_categories()
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return a list of default display categories.
|
||||
|
||||
This can be overridden by the user in the Home Assistant configuration.
|
||||
|
||||
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:
|
||||
"""Hold the configuration for Alexa."""
|
||||
|
||||
@ -80,6 +379,52 @@ class Config:
|
||||
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
|
||||
def async_handle_message(hass, config, message):
|
||||
"""Handle incoming API messages."""
|
||||
@ -100,7 +445,11 @@ def async_handle_message(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.
|
||||
|
||||
Async friendly.
|
||||
@ -128,6 +477,9 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
|
||||
if API_ENDPOINT in request:
|
||||
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
|
||||
|
||||
if context is not None:
|
||||
response[API_CONTEXT] = context
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@ -159,9 +511,9 @@ def async_api_discovery(hass, config, request):
|
||||
entity.entity_id)
|
||||
continue
|
||||
|
||||
class_data = MAPPING_COMPONENT.get(entity.domain)
|
||||
|
||||
if not class_data:
|
||||
try:
|
||||
entity_capabilities = _capabilities_for_entity(config, entity)
|
||||
except _UnknownEntityDomainError:
|
||||
continue
|
||||
|
||||
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)'
|
||||
description = scene_fmt.format(description)
|
||||
|
||||
display_categories = entity_conf.get(CONF_DISPLAY_CATEGORIES,
|
||||
class_data[0])
|
||||
|
||||
endpoint = {
|
||||
'displayCategories': [display_categories],
|
||||
'displayCategories': entity_capabilities.display_categories(),
|
||||
'additionalApplianceDetails': {},
|
||||
'endpointId': entity.entity_id.replace('.', '#'),
|
||||
'friendlyName': friendly_name,
|
||||
'description': description,
|
||||
'manufacturerName': 'Home Assistant',
|
||||
}
|
||||
actions = set()
|
||||
|
||||
# static actions
|
||||
if class_data[1]:
|
||||
actions |= set(class_data[1])
|
||||
|
||||
# dynamic actions
|
||||
if class_data[2]:
|
||||
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
|
||||
alexa_capabilities = entity_capabilities.capabilities()
|
||||
if not alexa_capabilities:
|
||||
_LOGGER.debug("Not exposing %s because it has no capabilities",
|
||||
entity.entity_id)
|
||||
continue
|
||||
endpoint['capabilities'] = alexa_capabilities
|
||||
discovery_endpoints.append(endpoint)
|
||||
|
||||
return api_message(
|
||||
@ -216,7 +549,7 @@ def async_api_discovery(hass, config, request):
|
||||
|
||||
|
||||
def extract_entity(funct):
|
||||
"""Decorator for extract entity object from request."""
|
||||
"""Decorate for extract entity object from request."""
|
||||
@asyncio.coroutine
|
||||
def async_api_entity_wrapper(hass, config, request):
|
||||
"""Process a turn on request."""
|
||||
@ -240,8 +573,6 @@ def extract_entity(funct):
|
||||
def async_api_turn_on(hass, config, request, entity):
|
||||
"""Process a turn on request."""
|
||||
domain = entity.domain
|
||||
if entity.domain == group.DOMAIN:
|
||||
domain = ha.DOMAIN
|
||||
|
||||
service = SERVICE_TURN_ON
|
||||
if entity.domain == cover.DOMAIN:
|
||||
@ -379,7 +710,7 @@ def async_api_decrease_color_temp(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
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))
|
||||
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
|
||||
@asyncio.coroutine
|
||||
def async_api_activate(hass, config, request, entity):
|
||||
"""Process a activate request."""
|
||||
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
|
||||
"""Process an activate request."""
|
||||
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
|
||||
}, 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'))
|
||||
@ -653,3 +1026,25 @@ def async_api_previous(hass, config, request, entity):
|
||||
data, blocking=False)
|
||||
|
||||
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]}
|
||||
)
|
||||
|
@ -7,13 +7,14 @@ https://home-assistant.io/components/apple_tv/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from typing import Sequence, TypeVar, Union
|
||||
|
||||
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.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
|
||||
|
||||
REQUIREMENTS = ['pyatv==0.3.9']
|
||||
@ -59,9 +60,9 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_HOST): 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_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)
|
||||
|
||||
@ -140,7 +141,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_service_handler(service):
|
||||
"""Handler for service calls."""
|
||||
"""Handle service calls."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
if service.service == SERVICE_SCAN:
|
||||
@ -167,7 +168,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
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, {
|
||||
CONF_NAME: info['name'],
|
||||
CONF_HOST: info['host'],
|
||||
@ -194,7 +195,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def _setup_atv(hass, atv_config):
|
||||
"""Setup an Apple TV."""
|
||||
"""Set up an Apple TV."""
|
||||
import pyatv
|
||||
name = atv_config.get(CONF_NAME)
|
||||
host = atv_config.get(CONF_HOST)
|
||||
@ -245,7 +246,7 @@ class AppleTVPowerManager:
|
||||
|
||||
@property
|
||||
def turned_on(self):
|
||||
"""If device is on or off."""
|
||||
"""Return true if device is on or off."""
|
||||
return self._is_on
|
||||
|
||||
def set_power_on(self, value):
|
||||
|
@ -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 voluptuous as vol
|
||||
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import (CONF_HOST,
|
||||
CONF_PORT, CONF_PASSWORD)
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
|
||||
async_dispatcher_send)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['asterisk_mbox==0.4.0']
|
||||
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
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({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@ -43,7 +43,7 @@ def setup(hass, config):
|
||||
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
|
||||
|
||||
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
@ -68,15 +68,14 @@ class AsteriskData(object):
|
||||
from asterisk_mbox.commands import CMD_MESSAGE_LIST
|
||||
|
||||
if command == CMD_MESSAGE_LIST:
|
||||
_LOGGER.info("AsteriskVM sent updated message list")
|
||||
self.messages = sorted(msg,
|
||||
key=lambda item: item['info']['origtime'],
|
||||
reverse=True)
|
||||
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
|
||||
self.messages)
|
||||
_LOGGER.debug("AsteriskVM sent updated message list")
|
||||
self.messages = sorted(
|
||||
msg, key=lambda item: item['info']['origtime'], reverse=True)
|
||||
async_dispatcher_send(
|
||||
self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
|
||||
|
||||
@callback
|
||||
def _request_messages(self):
|
||||
"""Handle changes to the mailbox."""
|
||||
_LOGGER.info("Requesting message list")
|
||||
_LOGGER.debug("Requesting message list")
|
||||
self.client.messages()
|
||||
|
@ -338,10 +338,9 @@ class AutomationEntity(ToggleEntity):
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_remove(self):
|
||||
"""Remove automation from HASS."""
|
||||
def async_will_remove_from_hass(self):
|
||||
"""Remove listeners when removing automation from HASS."""
|
||||
yield from self.async_turn_off()
|
||||
yield from super().async_remove()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_enable(self):
|
||||
|
@ -4,24 +4,21 @@ Support for Axis devices.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/axis/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.discovery import SERVICE_AXIS
|
||||
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
|
||||
CONF_EVENT, CONF_HOST, CONF_INCLUDE,
|
||||
CONF_NAME, CONF_PASSWORD, CONF_PORT,
|
||||
CONF_TRIGGER_TIME, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.const import (
|
||||
ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE,
|
||||
CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
|
||||
REQUIREMENTS = ['axis==14']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -81,10 +78,10 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
configurator = hass.components.configurator
|
||||
|
||||
def configuration_callback(callback_data):
|
||||
"""Called when config is submitted."""
|
||||
"""Call when configuration is submitted."""
|
||||
if CONF_INCLUDE not in callback_data:
|
||||
configurator.notify_errors(request_id,
|
||||
"Functionality mandatory.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Functionality mandatory.")
|
||||
return False
|
||||
|
||||
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
|
||||
@ -96,18 +93,20 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
try:
|
||||
device_config = DEVICE_SCHEMA(callback_data)
|
||||
except vol.Invalid:
|
||||
configurator.notify_errors(request_id,
|
||||
"Bad input, please check spelling.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Bad input, please check spelling.")
|
||||
return False
|
||||
|
||||
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[serialnumber] = dict(device_config)
|
||||
save_json(hass.config.path(CONFIG_FILE), config_file)
|
||||
configurator.request_done(request_id)
|
||||
else:
|
||||
configurator.notify_errors(request_id,
|
||||
"Failed to register, please try again.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Failed to register, please try again.")
|
||||
return False
|
||||
|
||||
title = '{} ({})'.format(name, host)
|
||||
@ -145,7 +144,7 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Common setup for Axis devices."""
|
||||
"""Set up for Axis devices."""
|
||||
def _shutdown(call): # pylint: disable=unused-argument
|
||||
"""Stop the event stream on shutdown."""
|
||||
for serialnumber, device in AXIS_DEVICES.items():
|
||||
@ -155,7 +154,7 @@ def setup(hass, config):
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||
|
||||
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]
|
||||
name = discovery_info['hostname']
|
||||
serialnumber = discovery_info['properties']['macaddress']
|
||||
@ -171,8 +170,8 @@ def setup(hass, config):
|
||||
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
|
||||
return False
|
||||
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])
|
||||
else:
|
||||
# New device, create configuration request for UI
|
||||
request_configuration(hass, config, name, host, serialnumber)
|
||||
@ -191,7 +190,7 @@ def setup(hass, config):
|
||||
if CONF_NAME not in device_config:
|
||||
device_config[CONF_NAME] = device
|
||||
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):
|
||||
"""Service to send a message."""
|
||||
@ -203,23 +202,21 @@ def setup(hass, config):
|
||||
call.data[SERVICE_PARAM])
|
||||
hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response)
|
||||
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
|
||||
|
||||
# Register service with Home Assistant.
|
||||
hass.services.register(DOMAIN,
|
||||
SERVICE_VAPIX_CALL,
|
||||
vapix_service,
|
||||
schema=SERVICE_SCHEMA)
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
def setup_device(hass, config, device_config):
|
||||
"""Set up device."""
|
||||
"""Set up an Axis device."""
|
||||
from axis import AxisDevice
|
||||
|
||||
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':
|
||||
event_config = {
|
||||
CONF_EVENT: event,
|
||||
@ -228,11 +225,8 @@ def setup_device(hass, config, device_config):
|
||||
CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME]
|
||||
}
|
||||
component = event.event_platform
|
||||
discovery.load_platform(hass,
|
||||
component,
|
||||
DOMAIN,
|
||||
event_config,
|
||||
config)
|
||||
discovery.load_platform(
|
||||
hass, component, DOMAIN, event_config, config)
|
||||
|
||||
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE],
|
||||
EVENT_TYPES))
|
||||
@ -243,7 +237,7 @@ def setup_device(hass, config, device_config):
|
||||
|
||||
if device.serial_number is None:
|
||||
# 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
|
||||
|
||||
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_PASSWORD: device_config[CONF_PASSWORD]
|
||||
}
|
||||
discovery.load_platform(hass,
|
||||
component,
|
||||
DOMAIN,
|
||||
camera_config,
|
||||
config)
|
||||
discovery.load_platform(
|
||||
hass, component, DOMAIN, camera_config, config)
|
||||
|
||||
AXIS_DEVICES[device.serial_number] = device
|
||||
if event_types:
|
||||
@ -273,9 +264,9 @@ class AxisDeviceEvent(Entity):
|
||||
def __init__(self, event_config):
|
||||
"""Initialize the event."""
|
||||
self.axis_event = event_config[CONF_EVENT]
|
||||
self._name = '{}_{}_{}'.format(event_config[CONF_NAME],
|
||||
self.axis_event.event_type,
|
||||
self.axis_event.id)
|
||||
self._name = '{}_{}_{}'.format(
|
||||
event_config[CONF_NAME], self.axis_event.event_type,
|
||||
self.axis_event.id)
|
||||
self.location = event_config[ATTR_LOCATION]
|
||||
self.axis_event.callback = self._update_callback
|
||||
|
||||
@ -296,7 +287,7 @@ class AxisDeviceEvent(Entity):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
"""Return the polling state. No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@ -3,23 +3,22 @@ Support for ADS binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation.
|
||||
https://home-assistant.io/components/binary_sensor.ads/
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
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__)
|
||||
|
||||
DEPENDENCIES = ['ads']
|
||||
DEFAULT_NAME = 'ADS binary sensor'
|
||||
|
||||
DEPENDENCIES = ['ads']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
@ -44,7 +43,7 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
"""Representation of ADS binary sensors."""
|
||||
|
||||
def __init__(self, ads_hub, name, ads_var, device_class):
|
||||
"""Initialize AdsBinarySensor entity."""
|
||||
"""Initialize ADS binary sensor."""
|
||||
self._name = name
|
||||
self._state = False
|
||||
self._device_class = device_class or 'moving'
|
||||
@ -56,15 +55,13 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
"""Register device notification."""
|
||||
def update(name, value):
|
||||
"""Handle device notifications."""
|
||||
_LOGGER.debug('Variable %s changed its value to %d',
|
||||
name, value)
|
||||
_LOGGER.debug('Variable %s changed its value to %d', name, value)
|
||||
self._state = value
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.hass.async_add_job(
|
||||
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
|
||||
def name(self):
|
||||
|
@ -4,13 +4,12 @@ Support for Axis binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.axis/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
from homeassistant.components.axis import (AxisDeviceEvent)
|
||||
from homeassistant.const import (CONF_TRIGGER_TIME)
|
||||
from homeassistant.components.axis import AxisDeviceEvent
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_TRIGGER_TIME
|
||||
from homeassistant.helpers.event import track_point_in_utc_time
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -28,7 +27,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
|
||||
"""Representation of a binary Axis event."""
|
||||
|
||||
def __init__(self, hass, event_config):
|
||||
"""Initialize the binary sensor."""
|
||||
"""Initialize the Axis binary sensor."""
|
||||
self.hass = hass
|
||||
self._state = False
|
||||
self._delay = event_config[CONF_TRIGGER_TIME]
|
||||
@ -56,7 +55,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
|
||||
# Set timer to wait until updating the state
|
||||
def _delay_update(now):
|
||||
"""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.schedule_update_ha_state()
|
||||
self._timer = None
|
||||
|
@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
sensors = []
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Initializing Client")
|
||||
_LOGGER.debug("Initializing client")
|
||||
client = concord232_client.Client('http://{}:{}'.format(host, port))
|
||||
client.zones = client.list_zones()
|
||||
client.last_zone_update = datetime.datetime.now()
|
||||
|
@ -4,7 +4,6 @@ Support for deCONZ binary sensor.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.deconz/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -17,7 +16,7 @@ DEPENDENCIES = ['deconz']
|
||||
|
||||
@asyncio.coroutine
|
||||
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:
|
||||
return
|
||||
|
||||
@ -25,8 +24,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
sensors = hass.data[DECONZ_DATA].sensors
|
||||
entities = []
|
||||
|
||||
for sensor in sensors.values():
|
||||
if sensor.type in DECONZ_BINARY_SENSOR:
|
||||
for key in sorted(sensors.keys(), key=int):
|
||||
sensor = sensors[key]
|
||||
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
|
||||
entities.append(DeconzBinarySensor(sensor))
|
||||
async_add_devices(entities, True)
|
||||
|
||||
@ -35,7 +35,7 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a binary 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
|
||||
|
||||
@asyncio.coroutine
|
||||
@ -67,7 +67,7 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Class of the sensor."""
|
||||
"""Return the class of the sensor."""
|
||||
return self._sensor.sensor_class
|
||||
|
||||
@property
|
||||
|
@ -238,6 +238,5 @@ class FlicButton(BinarySensorDevice):
|
||||
import pyflic
|
||||
|
||||
if connection_status == pyflic.ConnectionStatus.Disconnected:
|
||||
_LOGGER.info("Button (%s) disconnected. Reason: %s",
|
||||
self.address, disconnect_reason)
|
||||
self.remove()
|
||||
_LOGGER.warning("Button (%s) disconnected. Reason: %s",
|
||||
self.address, disconnect_reason)
|
||||
|
@ -1,63 +1,63 @@
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
|
||||
'contactsensor': 'opening'}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveBinarySensorEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
"""Representation of a Hive binary sensor."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the hive sensor."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.node_device_type = hivedevice["Hive_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return self.node_name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.session.sensor.get_state(self.node_id,
|
||||
self.node_device_type)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data frome Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
|
||||
'contactsensor': 'opening'}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveBinarySensorEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
"""Representation of a Hive binary sensor."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the hive sensor."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.node_device_type = hivedevice["Hive_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return self.node_name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.session.sensor.get_state(self.node_id,
|
||||
self.node_device_type)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data frome Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
|
95
homeassistant/components/binary_sensor/ihc.py
Normal file
95
homeassistant/components/binary_sensor/ihc.py
Normal 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()
|
@ -5,12 +5,13 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.knx/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \
|
||||
KNXAutomation
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \
|
||||
BinarySensorDevice
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.knx import (
|
||||
ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -53,20 +54,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
@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 binary sensor(s) for KNX platform."""
|
||||
if DATA_KNX not in hass.data \
|
||||
or not hass.data[DATA_KNX].initialized:
|
||||
return False
|
||||
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
|
||||
return
|
||||
|
||||
if discovery_info is not None:
|
||||
async_add_devices_discovery(hass, discovery_info, async_add_devices)
|
||||
else:
|
||||
async_add_devices_config(hass, config, async_add_devices)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
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
|
||||
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)
|
||||
import xknx
|
||||
binary_sensor = xknx.devices.BinarySensor(
|
||||
@ -108,7 +105,7 @@ class KNXBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a KNX binary sensor."""
|
||||
|
||||
def __init__(self, hass, device):
|
||||
"""Initialization of KNXBinarySensor."""
|
||||
"""Initialize of KNX binary sensor."""
|
||||
self.device = device
|
||||
self.hass = hass
|
||||
self.async_register_callbacks()
|
||||
@ -119,7 +116,7 @@ class KNXBinarySensor(BinarySensorDevice):
|
||||
"""Register callbacks to update hass after device was changed."""
|
||||
@asyncio.coroutine
|
||||
def after_update_callback(device):
|
||||
"""Callback after device was updated."""
|
||||
"""Call after device was updated."""
|
||||
# pylint: disable=unused-argument
|
||||
yield from self.async_update_ha_state()
|
||||
self.device.register_device_updated_cb(after_update_callback)
|
||||
|
@ -36,7 +36,7 @@ class MaxCubeShutter(BinarySensorDevice):
|
||||
def __init__(self, hass, name, rf_address):
|
||||
"""Initialize MAX! Cube BinarySensorDevice."""
|
||||
self._name = name
|
||||
self._sensor_type = 'opening'
|
||||
self._sensor_type = 'window'
|
||||
self._rf_address = rf_address
|
||||
self._cubehandle = hass.data[MAXCUBE_HANDLE]
|
||||
self._state = STATE_UNKNOWN
|
||||
|
85
homeassistant/components/binary_sensor/mychevy.py
Normal file
85
homeassistant/components/binary_sensor/mychevy.py
Normal 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
|
@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mysensors/
|
||||
"""
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
|
||||
BinarySensorDevice)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES, DOMAIN, BinarySensorDevice)
|
||||
from homeassistant.const import STATE_ON
|
||||
|
||||
|
||||
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(
|
||||
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
|
||||
add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(
|
||||
mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Represent the value of a MySensors Binary Sensor child node."""
|
||||
class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Representation of a MySensors Binary Sensor child node."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mystrom/
|
||||
import asyncio
|
||||
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.const import HTTP_UNPROCESSABLE_ENTITY
|
||||
|
||||
@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView):
|
||||
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
return res
|
||||
|
||||
|
@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.raspihats/
|
||||
"""
|
||||
import logging
|
||||
|
||||
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 (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice
|
||||
)
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.raspihats import (
|
||||
CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX,
|
||||
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException
|
||||
)
|
||||
CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX,
|
||||
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__)
|
||||
|
||||
@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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]
|
||||
binary_sensors = []
|
||||
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:
|
||||
_LOGGER.error(
|
||||
"Failed to register " + board + "I2CHat@" + hex(address) + " "
|
||||
+ str(ex)
|
||||
)
|
||||
_LOGGER.error("Failed to register %s I2CHat@%s %s",
|
||||
board, hex(address), str(ex))
|
||||
add_devices(binary_sensors)
|
||||
|
||||
|
||||
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
|
||||
|
||||
def __init__(self, address, channel, name, invert_logic, device_class):
|
||||
"""Initialize sensor."""
|
||||
"""Initialize the raspihats sensor."""
|
||||
self._address = address
|
||||
self._channel = channel
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._invert_logic = invert_logic
|
||||
self._device_class = device_class
|
||||
self._state = self.I2C_HATS_MANAGER.read_di(
|
||||
self._address,
|
||||
self._channel
|
||||
)
|
||||
self._address, self._channel)
|
||||
|
||||
def online_callback():
|
||||
"""Callback fired when board is online."""
|
||||
"""Call fired when board is online."""
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_online_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
online_callback
|
||||
)
|
||||
self._address, self._channel, online_callback)
|
||||
|
||||
def edge_callback(state):
|
||||
"""Read digital input state."""
|
||||
@ -105,10 +97,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_di_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
edge_callback
|
||||
)
|
||||
self._address, self._channel, edge_callback)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@ -122,7 +111,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling not needed for this sensor."""
|
||||
"""No polling needed for this sensor."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@ -1,38 +1,36 @@
|
||||
"""
|
||||
Support for RFXtrx binary sensors.
|
||||
|
||||
Lighting4 devices (sensors based on PT2262 encoder) are supported and
|
||||
tested. Other types may need some work.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.rfxtrx/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
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.helpers import event as evt
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.rfxtrx import (
|
||||
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT,
|
||||
CONF_OFF_DELAY, CONF_DATA_BITS, CONF_DEVICES)
|
||||
from homeassistant.util import slugify
|
||||
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
|
||||
CONF_FIRE_EVENT, CONF_OFF_DELAY)
|
||||
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
|
||||
|
||||
|
||||
DEPENDENCIES = ["rfxtrx"]
|
||||
from homeassistant.util import slugify
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['rfxtrx']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_DEVICES, default={}): {
|
||||
cv.string: vol.Schema({
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.string,
|
||||
vol.Optional(CONF_NAME, default=None): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS, default=None):
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_OFF_DELAY, default=None):
|
||||
vol.Any(cv.time_period, cv.positive_timedelta),
|
||||
@ -45,8 +43,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Setup the Binary Sensor platform to rfxtrx."""
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Binary Sensor platform to RFXtrx."""
|
||||
import RFXtrx as rfxtrxmod
|
||||
sensors = []
|
||||
|
||||
@ -58,29 +56,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
continue
|
||||
|
||||
if entity[CONF_DATA_BITS] is not None:
|
||||
_LOGGER.debug("Masked device id: %s",
|
||||
rfxtrx.get_pt2262_deviceid(device_id,
|
||||
entity[CONF_DATA_BITS]))
|
||||
_LOGGER.debug(
|
||||
"Masked device id: %s", rfxtrx.get_pt2262_deviceid(
|
||||
device_id, entity[CONF_DATA_BITS]))
|
||||
|
||||
_LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)",
|
||||
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
|
||||
|
||||
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
|
||||
entity[CONF_DEVICE_CLASS],
|
||||
entity[CONF_FIRE_EVENT],
|
||||
entity[CONF_OFF_DELAY],
|
||||
entity[CONF_DATA_BITS],
|
||||
entity[CONF_COMMAND_ON],
|
||||
entity[CONF_COMMAND_OFF])
|
||||
device = RfxtrxBinarySensor(
|
||||
event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS],
|
||||
entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY],
|
||||
entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON],
|
||||
entity[CONF_COMMAND_OFF])
|
||||
device.hass = hass
|
||||
sensors.append(device)
|
||||
rfxtrx.RFX_DEVICES[device_id] = device
|
||||
|
||||
add_devices_callback(sensors)
|
||||
add_devices(sensors)
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
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):
|
||||
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)
|
||||
if poss_dev is not None:
|
||||
poss_id = slugify(poss_dev.event.device.id_string.lower())
|
||||
_LOGGER.debug("Found possible matching deviceid %s.",
|
||||
poss_id)
|
||||
_LOGGER.debug(
|
||||
"Found possible matching device ID: %s", poss_id)
|
||||
|
||||
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
|
||||
sensor = RfxtrxBinarySensor(event, pkt_id)
|
||||
sensor.hass = hass
|
||||
rfxtrx.RFX_DEVICES[device_id] = sensor
|
||||
add_devices_callback([sensor])
|
||||
_LOGGER.info("Added binary sensor %s "
|
||||
"(Device_id: %s Class: %s Sub: %s)",
|
||||
pkt_id,
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype)
|
||||
add_devices([sensor])
|
||||
_LOGGER.info(
|
||||
"Added binary sensor %s (Device ID: %s Class: %s Sub: %s)",
|
||||
pkt_id, slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__, event.device.subtype)
|
||||
|
||||
elif not isinstance(sensor, RfxtrxBinarySensor):
|
||||
return
|
||||
else:
|
||||
_LOGGER.debug("Binary sensor update "
|
||||
"(Device_id: %s Class: %s Sub: %s)",
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype)
|
||||
_LOGGER.debug(
|
||||
"Binary sensor update (Device ID: %s Class: %s Sub: %s)",
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__, event.device.subtype)
|
||||
|
||||
if sensor.is_lighting4:
|
||||
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.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:
|
||||
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
||||
class RfxtrxBinarySensor(BinarySensorDevice):
|
||||
"""An Rfxtrx binary sensor."""
|
||||
"""A representation of a RFXtrx binary sensor."""
|
||||
|
||||
def __init__(self, event, name, device_class=None,
|
||||
should_fire=False, off_delay=None, data_bits=None,
|
||||
cmd_on=None, cmd_off=None):
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the RFXtrx sensor."""
|
||||
self.event = event
|
||||
self._name = name
|
||||
self._should_fire_event = should_fire
|
||||
@ -172,8 +162,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
|
||||
|
||||
if data_bits is not None:
|
||||
self._masked_id = rfxtrx.get_pt2262_deviceid(
|
||||
event.device.id_string.lower(),
|
||||
data_bits)
|
||||
event.device.id_string.lower(), data_bits)
|
||||
else:
|
||||
self._masked_id = None
|
||||
|
||||
|
@ -8,18 +8,17 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.rpi_pfio as rpi_pfio
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
import homeassistant.components.rpi_pfio as rpi_pfio
|
||||
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_NAME = 'name'
|
||||
ATTR_INVERT_LOGIC = 'invert_logic'
|
||||
ATTR_SETTLE_TIME = 'settle_time'
|
||||
CONF_INVERT_LOGIC = 'invert_logic'
|
||||
CONF_PORTS = 'ports'
|
||||
CONF_SETTLE_TIME = 'settle_time'
|
||||
|
||||
DEFAULT_INVERT_LOGIC = False
|
||||
DEFAULT_SETTLE_TIME = 20
|
||||
@ -27,27 +26,27 @@ DEFAULT_SETTLE_TIME = 20
|
||||
DEPENDENCIES = ['rpi_pfio']
|
||||
|
||||
PORT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_NAME, default=None): cv.string,
|
||||
vol.Optional(ATTR_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
|
||||
vol.Optional(CONF_NAME, default=None): cv.string,
|
||||
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
|
||||
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({
|
||||
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):
|
||||
"""Setup the PiFace Digital Input devices."""
|
||||
"""Set up the PiFace Digital Input devices."""
|
||||
binary_sensors = []
|
||||
ports = config.get('ports')
|
||||
ports = config.get(CONF_PORTS)
|
||||
for port, port_entity in ports.items():
|
||||
name = port_entity[ATTR_NAME]
|
||||
settle_time = port_entity[ATTR_SETTLE_TIME] / 1000
|
||||
invert_logic = port_entity[ATTR_INVERT_LOGIC]
|
||||
name = port_entity[CONF_NAME]
|
||||
settle_time = port_entity[CONF_SETTLE_TIME] / 1000
|
||||
invert_logic = port_entity[CONF_INVERT_LOGIC]
|
||||
|
||||
binary_sensors.append(RPiPFIOBinarySensor(
|
||||
hass, port, name, settle_time, invert_logic))
|
||||
|
@ -4,46 +4,49 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.spc/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.spc import (
|
||||
ATTR_DISCOVER_DEVICES, DATA_REGISTRY)
|
||||
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__)
|
||||
|
||||
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion',
|
||||
'1': 'opening',
|
||||
'3': 'smoke'}
|
||||
SPC_TYPE_TO_DEVICE_CLASS = {
|
||||
'0': 'motion',
|
||||
'1': 'opening',
|
||||
'3': 'smoke',
|
||||
}
|
||||
|
||||
|
||||
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF,
|
||||
'1': STATE_ON}
|
||||
SPC_INPUT_TO_SENSOR_STATE = {
|
||||
'0': STATE_OFF,
|
||||
'1': STATE_ON,
|
||||
}
|
||||
|
||||
|
||||
def _get_device_class(spc_type):
|
||||
"""Get the device class."""
|
||||
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
|
||||
|
||||
|
||||
def _get_sensor_state(spc_input):
|
||||
"""Get the sensor state."""
|
||||
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
|
||||
|
||||
|
||||
def _create_sensor(hass, zone):
|
||||
return SpcBinarySensor(zone_id=zone['id'],
|
||||
name=zone['zone_name'],
|
||||
state=_get_sensor_state(zone['input']),
|
||||
device_class=_get_device_class(zone['type']),
|
||||
spc_registry=hass.data[DATA_REGISTRY])
|
||||
"""Create a SPC sensor."""
|
||||
return SpcBinarySensor(
|
||||
zone_id=zone['id'], name=zone['zone_name'],
|
||||
state=_get_sensor_state(zone['input']),
|
||||
device_class=_get_device_class(zone['type']),
|
||||
spc_registry=hass.data[DATA_REGISTRY])
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the SPC binary sensor."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
return
|
||||
@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices,
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize the sensor device."""
|
||||
@ -74,7 +77,7 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the device."""
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
@ -85,7 +88,7 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def hidden(self) -> bool:
|
||||
"""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
|
||||
|
||||
@property
|
||||
@ -95,5 +98,5 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""The device class."""
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
@ -4,15 +4,15 @@ Support for Taps Affs.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.tapsaff/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (CONF_NAME)
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['tapsaff==0.1.3']
|
||||
|
||||
@ -67,7 +67,7 @@ class TapsAffData(object):
|
||||
"""Class for handling the data retrieval for pins."""
|
||||
|
||||
def __init__(self, location):
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the data object."""
|
||||
from tapsaff import TapsAff
|
||||
|
||||
self._is_taps_aff = None
|
||||
|
@ -28,7 +28,7 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
|
||||
"""Implement an Tesla binary sensor for parking and charger."""
|
||||
|
||||
def __init__(self, tesla_device, controller, sensor_type):
|
||||
"""Initialisation of binary sensor."""
|
||||
"""Initialise of a Tesla binary sensor."""
|
||||
super().__init__(tesla_device, controller)
|
||||
self._state = False
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
@ -6,15 +6,15 @@ https://home-assistant.io/components/binary_sensor.verisure/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.verisure import CONF_DOOR_WINDOW
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Verisure binary sensors."""
|
||||
"""Set up the Verisure binary sensors."""
|
||||
sensors = []
|
||||
hub.update_overview()
|
||||
|
||||
@ -27,10 +27,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class VerisureDoorWindowSensor(BinarySensorDevice):
|
||||
"""Verisure door window sensor."""
|
||||
"""Representation of a Verisure door window sensor."""
|
||||
|
||||
def __init__(self, device_label):
|
||||
"""Initialize the modbus coil sensor."""
|
||||
"""Initialize the Verisure door window sensor."""
|
||||
self._device_label = device_label
|
||||
|
||||
@property
|
||||
|
@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
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]
|
||||
|
||||
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:
|
||||
_LOGGER.error("Subscription %s not found", subscription)
|
||||
return False
|
||||
return
|
||||
|
||||
add_devices([VultrBinarySensor(vultr, subscription, name)], True)
|
||||
|
||||
@ -48,7 +48,7 @@ class VultrBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Vultr subscription sensor."""
|
||||
|
||||
def __init__(self, vultr, subscription, name):
|
||||
"""Initialize a new Vultr sensor."""
|
||||
"""Initialize a new Vultr binary sensor."""
|
||||
self._vultr = vultr
|
||||
self._name = name
|
||||
|
||||
|
@ -8,7 +8,7 @@ import asyncio
|
||||
import logging
|
||||
|
||||
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__)
|
||||
|
||||
@ -16,18 +16,18 @@ DEPENDENCIES = ['wink']
|
||||
|
||||
# These are the available sensors mapped to binary_sensor class
|
||||
SENSOR_TYPES = {
|
||||
'opened': 'opening',
|
||||
'brightness': 'light',
|
||||
'vibration': 'vibration',
|
||||
'loudness': 'sound',
|
||||
'noise': 'sound',
|
||||
'capturing_audio': 'sound',
|
||||
'liquid_detected': 'moisture',
|
||||
'motion': 'motion',
|
||||
'presence': 'occupancy',
|
||||
'capturing_video': None,
|
||||
'co_detected': 'gas',
|
||||
'liquid_detected': 'moisture',
|
||||
'loudness': 'sound',
|
||||
'motion': 'motion',
|
||||
'noise': 'sound',
|
||||
'opened': 'opening',
|
||||
'presence': 'occupancy',
|
||||
'smoke_detected': 'smoke',
|
||||
'capturing_video': None
|
||||
'vibration': 'vibration',
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
|
||||
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
|
||||
@property
|
||||
@ -118,7 +118,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
return super().device_state_attributes
|
||||
|
||||
|
||||
@ -127,7 +127,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['test_activated'] = self.wink.test_activated()
|
||||
return _attributes
|
||||
@ -138,11 +138,18 @@ class WinkHub(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['update_needed'] = self.wink.update_needed()
|
||||
_attributes['firmware_version'] = self.wink.firmware_version()
|
||||
_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
|
||||
|
||||
|
||||
@ -170,7 +177,7 @@ class WinkButton(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['pressed'] = self.wink.pressed()
|
||||
_attributes['long_pressed'] = self.wink.long_pressed()
|
||||
|
@ -17,17 +17,21 @@ import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['holidays==0.8.1']
|
||||
REQUIREMENTS = ['holidays==0.9.3']
|
||||
|
||||
# List of all countries currently supported by holidays
|
||||
# There seems to be no way to get the list out at runtime
|
||||
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA',
|
||||
'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England',
|
||||
'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE',
|
||||
'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL',
|
||||
'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO',
|
||||
'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain',
|
||||
'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales']
|
||||
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada',
|
||||
'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK',
|
||||
'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland',
|
||||
'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland',
|
||||
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
|
||||
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
|
||||
'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_PROVINCE = 'province'
|
||||
CONF_WORKDAYS = 'workdays'
|
||||
|
@ -101,7 +101,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if DENSITY in data:
|
||||
self._density = int(data.get(DENSITY))
|
||||
@ -139,8 +139,16 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""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
|
||||
if NO_MOTION in data: # handle push from the hub
|
||||
self._no_motion_since = data[NO_MOTION]
|
||||
@ -186,7 +194,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
self._should_poll = False
|
||||
if NO_CLOSE in data: # handle push from the hub
|
||||
@ -219,7 +227,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
|
||||
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
|
||||
xiaomi_hub, 'status', 'moisture')
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
self._should_poll = False
|
||||
|
||||
@ -256,7 +264,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if DENSITY in data:
|
||||
self._density = int(data.get(DENSITY))
|
||||
@ -293,7 +301,7 @@ class XiaomiButton(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
value = data.get(self._data_key)
|
||||
if value is None:
|
||||
@ -343,7 +351,7 @@ class XiaomiCube(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if 'status' in data:
|
||||
self._hass.bus.fire('cube_action', {
|
||||
|
@ -4,18 +4,18 @@ Support for WebDav Calendar.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/calendar.caldav/
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.calendar import (
|
||||
CalendarEventDevice, PLATFORM_SCHEMA)
|
||||
PLATFORM_SCHEMA, CalendarEventDevice)
|
||||
from homeassistant.const import (
|
||||
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']
|
||||
|
||||
@ -39,9 +39,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_CUSTOM_CALENDARS, default=[]):
|
||||
vol.All(cv.ensure_list, vol.Schema([
|
||||
vol.Schema({
|
||||
vol.Required(CONF_NAME): 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."""
|
||||
import caldav
|
||||
|
||||
client = caldav.DAVClient(config.get(CONF_URL),
|
||||
None,
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD))
|
||||
url = config.get(CONF_URL)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
client = caldav.DAVClient(url, None, username, password)
|
||||
|
||||
# Retrieve all the remote calendars
|
||||
calendars = client.principal().calendars()
|
||||
|
||||
calendar_devices = []
|
||||
@ -70,8 +70,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
||||
_LOGGER.debug("Ignoring calendar '%s'", calendar.name)
|
||||
continue
|
||||
|
||||
# Create additional calendars based on custom filtering
|
||||
# rules
|
||||
# Create additional calendars based on custom filtering rules
|
||||
for cust_calendar in config.get(CONF_CUSTOM_CALENDARS):
|
||||
# Check that the base calendar matches
|
||||
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(
|
||||
WebDavCalendarEventDevice(hass,
|
||||
device_data,
|
||||
calendar,
|
||||
True,
|
||||
cust_calendar.get(CONF_SEARCH))
|
||||
)
|
||||
WebDavCalendarEventDevice(
|
||||
hass, device_data, calendar, True,
|
||||
cust_calendar.get(CONF_SEARCH)))
|
||||
|
||||
# Create a default calendar if there was no custom one
|
||||
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)
|
||||
)
|
||||
|
||||
# Finally add all the calendars we've created
|
||||
add_devices(calendar_devices)
|
||||
|
||||
|
||||
class WebDavCalendarEventDevice(CalendarEventDevice):
|
||||
"""A device for getting the next Task from a WebDav Calendar."""
|
||||
|
||||
def __init__(self,
|
||||
hass,
|
||||
device_data,
|
||||
calendar,
|
||||
all_day=False,
|
||||
def __init__(self, hass, device_data, calendar, all_day=False,
|
||||
search=None):
|
||||
"""Create the WebDav Calendar Event Device."""
|
||||
self.data = WebDavCalendarData(calendar, all_day, search)
|
||||
@ -167,9 +158,7 @@ class WebDavCalendarData(object):
|
||||
if vevent is None:
|
||||
_LOGGER.debug(
|
||||
"No matching event found in the %d results for %s",
|
||||
len(results),
|
||||
self.calendar.name,
|
||||
)
|
||||
len(results), self.calendar.name)
|
||||
self.event = None
|
||||
return True
|
||||
|
||||
|
@ -4,29 +4,28 @@ Support for Todoist task management (https://todoist.com).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/calendar.todoist/
|
||||
"""
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.calendar import (
|
||||
CalendarEventDevice, DOMAIN, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.google import (
|
||||
CONF_DEVICE_ID)
|
||||
from homeassistant.const import (
|
||||
CONF_ID, CONF_NAME, CONF_TOKEN)
|
||||
DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice)
|
||||
from homeassistant.components.google import CONF_DEVICE_ID
|
||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||
from homeassistant.util import dt
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.util import Throttle, dt
|
||||
|
||||
REQUIREMENTS = ['todoist-python==7.0.17']
|
||||
|
||||
_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?
|
||||
ALL_DAY = 'all_day'
|
||||
# Attribute: All tasks in this project
|
||||
@ -78,20 +77,15 @@ SUMMARY = 'summary'
|
||||
TASKS = 'items'
|
||||
|
||||
SERVICE_NEW_TASK = 'todoist_new_task'
|
||||
|
||||
NEW_TASK_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONTENT): cv.string,
|
||||
vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower),
|
||||
vol.Optional(LABELS): cv.ensure_list_csv,
|
||||
vol.Optional(PRIORITY): vol.All(vol.Coerce(int),
|
||||
vol.Range(min=1, max=4)),
|
||||
vol.Optional(DUE_DATE): cv.string
|
||||
vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
|
||||
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({
|
||||
vol.Required(CONF_TOKEN): cv.string,
|
||||
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):
|
||||
"""Setup the Todoist platform."""
|
||||
# Check token:
|
||||
"""Set up the Todoist platform."""
|
||||
token = config.get(CONF_TOKEN)
|
||||
|
||||
# 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)
|
||||
|
||||
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_id = project_id_lookup[project_name]
|
||||
|
||||
@ -528,8 +521,7 @@ class TodoistProjectData(object):
|
||||
# Let's set our "due date" to tomorrow
|
||||
self.event[END] = {
|
||||
DATETIME: (
|
||||
datetime.utcnow() +
|
||||
timedelta(days=1)
|
||||
datetime.utcnow() + timedelta(days=1)
|
||||
).strftime(DATE_STR_FORMAT)
|
||||
}
|
||||
_LOGGER.debug("Updated %s", self._name)
|
||||
|
@ -124,15 +124,15 @@ def async_setup(hass, config):
|
||||
"""Set up the camera component."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||
|
||||
hass.http.register_view(CameraImageView(component.entities))
|
||||
hass.http.register_view(CameraMjpegStream(component.entities))
|
||||
hass.http.register_view(CameraImageView(component))
|
||||
hass.http.register_view(CameraMjpegStream(component))
|
||||
|
||||
yield from component.async_setup(config)
|
||||
|
||||
@callback
|
||||
def update_tokens(time):
|
||||
"""Update tokens of the entities."""
|
||||
for entity in component.entities.values():
|
||||
for entity in component.entities:
|
||||
entity.async_update_token()
|
||||
hass.async_add_job(entity.async_update_ha_state())
|
||||
|
||||
@ -358,14 +358,14 @@ class CameraView(HomeAssistantView):
|
||||
|
||||
requires_auth = False
|
||||
|
||||
def __init__(self, entities):
|
||||
def __init__(self, component):
|
||||
"""Initialize a basic camera view."""
|
||||
self.entities = entities
|
||||
self.component = component
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request, entity_id):
|
||||
"""Start a GET request."""
|
||||
camera = self.entities.get(entity_id)
|
||||
camera = self.component.get_entity(entity_id)
|
||||
|
||||
if camera is None:
|
||||
status = 404 if request[KEY_AUTHENTICATED] else 401
|
||||
|
@ -75,7 +75,9 @@ class ArloCam(Camera):
|
||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
|
||||
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 = {}
|
||||
|
||||
def camera_image(self):
|
||||
|
@ -6,11 +6,11 @@ https://home-assistant.io/components/camera.axis/
|
||||
"""
|
||||
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 (
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -20,6 +20,7 @@ DEPENDENCIES = [DOMAIN]
|
||||
|
||||
|
||||
def _get_image_url(host, port, mode):
|
||||
"""Set the URL to get the image."""
|
||||
if mode == 'mjpeg':
|
||||
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
|
||||
elif mode == 'single':
|
||||
@ -27,34 +28,32 @@ def _get_image_url(host, port, mode):
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Axis camera."""
|
||||
"""Set up the Axis camera."""
|
||||
camera_config = {
|
||||
CONF_NAME: discovery_info[CONF_NAME],
|
||||
CONF_USERNAME: discovery_info[CONF_USERNAME],
|
||||
CONF_PASSWORD: discovery_info[CONF_PASSWORD],
|
||||
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST],
|
||||
str(discovery_info[CONF_PORT]),
|
||||
'mjpeg'),
|
||||
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST],
|
||||
str(discovery_info[CONF_PORT]),
|
||||
'single'),
|
||||
CONF_MJPEG_URL: _get_image_url(
|
||||
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
|
||||
'mjpeg'),
|
||||
CONF_STILL_IMAGE_URL: _get_image_url(
|
||||
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
|
||||
'single'),
|
||||
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
|
||||
}
|
||||
add_devices([AxisCamera(hass,
|
||||
camera_config,
|
||||
str(discovery_info[CONF_PORT]))])
|
||||
add_devices([AxisCamera(
|
||||
hass, camera_config, str(discovery_info[CONF_PORT]))])
|
||||
|
||||
|
||||
class AxisCamera(MjpegCamera):
|
||||
"""AxisCamera class."""
|
||||
"""Representation of a Axis camera."""
|
||||
|
||||
def __init__(self, hass, config, port):
|
||||
"""Initialize Axis Communications camera component."""
|
||||
super().__init__(hass, config)
|
||||
self.port = port
|
||||
dispatcher_connect(hass,
|
||||
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
|
||||
self._new_ip)
|
||||
dispatcher_connect(
|
||||
hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip)
|
||||
|
||||
def _new_ip(self, host):
|
||||
"""Set new IP for video stream."""
|
||||
|
@ -4,21 +4,21 @@ Support for Blink system camera.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.blink/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
import requests
|
||||
|
||||
from homeassistant.components.blink import DOMAIN
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a Blink Camera."""
|
||||
@ -45,7 +45,7 @@ class BlinkCamera(Camera):
|
||||
self.notifications = self.data.cameras[self._name].notifications
|
||||
self.response = None
|
||||
|
||||
_LOGGER.info("Initialized blink camera %s", self._name)
|
||||
_LOGGER.debug("Initialized blink camera %s", self._name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -55,7 +55,7 @@ class BlinkCamera(Camera):
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def request_image(self):
|
||||
"""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()
|
||||
header = self.data.cameras[self._name].header
|
||||
self.response = requests.get(image_url, headers=header, stream=True)
|
||||
@ -68,7 +68,7 @@ class BlinkCamera(Camera):
|
||||
# We detected motion at some point
|
||||
self.data.last_motion()
|
||||
self.notifications = notifs
|
||||
# returning motion image currently not working
|
||||
# Returning motion image currently not working
|
||||
# return self.data.cameras[self._name].motion['image']
|
||||
elif notifs < self.notifications:
|
||||
self.notifications = notifs
|
||||
|
@ -44,6 +44,8 @@ class FoscamCam(Camera):
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a Foscam camera."""
|
||||
from libpyfoscam import FoscamCamera
|
||||
|
||||
super(FoscamCam, self).__init__()
|
||||
|
||||
ip_address = device_info.get(CONF_IP)
|
||||
@ -53,10 +55,8 @@ class FoscamCam(Camera):
|
||||
self._name = device_info.get(CONF_NAME)
|
||||
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):
|
||||
"""Return a still image response from the camera."""
|
||||
@ -75,20 +75,20 @@ class FoscamCam(Camera):
|
||||
|
||||
def enable_motion_detection(self):
|
||||
"""Enable motion detection in camera."""
|
||||
ret, err = self._foscam_session.enable_motion_detection()
|
||||
if ret == FOSCAM_COMM_ERROR:
|
||||
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
|
||||
self._motion_status = True
|
||||
else:
|
||||
try:
|
||||
ret = self._foscam_session.enable_motion_detection()
|
||||
self._motion_status = ret == FOSCAM_COMM_ERROR
|
||||
except TypeError:
|
||||
_LOGGER.debug("Communication problem")
|
||||
self._motion_status = False
|
||||
|
||||
def disable_motion_detection(self):
|
||||
"""Disable motion detection."""
|
||||
ret, err = self._foscam_session.disable_motion_detection()
|
||||
if ret == FOSCAM_COMM_ERROR:
|
||||
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
|
||||
self._motion_status = True
|
||||
else:
|
||||
try:
|
||||
ret = self._foscam_session.disable_motion_detection()
|
||||
self._motion_status = ret == FOSCAM_COMM_ERROR
|
||||
except TypeError:
|
||||
_LOGGER.debug("Communication problem")
|
||||
self._motion_status = False
|
||||
|
||||
@property
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
||||
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
from homeassistant.components.ffmpeg import (
|
||||
DATA_FFMPEG)
|
||||
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import (
|
||||
async_aiohttp_proxy_stream)
|
||||
@ -31,6 +31,7 @@ DEFAULT_NAME = 'ONVIF Camera'
|
||||
DEFAULT_PORT = 5000
|
||||
DEFAULT_USERNAME = 'admin'
|
||||
DEFAULT_PASSWORD = '888888'
|
||||
DEFAULT_ARGUMENTS = '-q:v 2'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
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_USERNAME, default=DEFAULT_USERNAME): cv.string,
|
||||
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__()
|
||||
|
||||
self._name = config.get(CONF_NAME)
|
||||
self._ffmpeg_arguments = '-q:v 2'
|
||||
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
|
||||
media = ONVIFService(
|
||||
'http://{}:{}/onvif/device_service'.format(
|
||||
config.get(CONF_HOST), config.get(CONF_PORT)),
|
||||
|
113
homeassistant/components/camera/xeoma.py
Executable file
113
homeassistant/components/camera/xeoma.py
Executable 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
|
@ -499,53 +499,54 @@ class ClimateDevice(Entity):
|
||||
self.precision),
|
||||
}
|
||||
|
||||
supported_features = self.supported_features
|
||||
if self.target_temperature_step is not None:
|
||||
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
|
||||
|
||||
target_temp_high = self.target_temperature_high
|
||||
if target_temp_high is not None:
|
||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
self.hass, self.target_temperature_high, self.temperature_unit,
|
||||
self.precision)
|
||||
|
||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
self.hass, self.target_temperature_low, self.temperature_unit,
|
||||
self.precision)
|
||||
|
||||
humidity = self.target_humidity
|
||||
if humidity is not None:
|
||||
data[ATTR_HUMIDITY] = humidity
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY:
|
||||
data[ATTR_HUMIDITY] = self.target_humidity
|
||||
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
fan_mode = self.current_fan_mode
|
||||
if fan_mode is not None:
|
||||
data[ATTR_FAN_MODE] = fan_mode
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
if supported_features & SUPPORT_FAN_MODE:
|
||||
data[ATTR_FAN_MODE] = self.current_fan_mode
|
||||
if self.fan_list:
|
||||
data[ATTR_FAN_LIST] = self.fan_list
|
||||
|
||||
operation_mode = self.current_operation
|
||||
if operation_mode is not None:
|
||||
data[ATTR_OPERATION_MODE] = operation_mode
|
||||
if supported_features & SUPPORT_OPERATION_MODE:
|
||||
data[ATTR_OPERATION_MODE] = self.current_operation
|
||||
if self.operation_list:
|
||||
data[ATTR_OPERATION_LIST] = self.operation_list
|
||||
|
||||
is_hold = self.current_hold_mode
|
||||
if is_hold is not None:
|
||||
data[ATTR_HOLD_MODE] = is_hold
|
||||
if supported_features & SUPPORT_HOLD_MODE:
|
||||
data[ATTR_HOLD_MODE] = self.current_hold_mode
|
||||
|
||||
swing_mode = self.current_swing_mode
|
||||
if swing_mode is not None:
|
||||
data[ATTR_SWING_MODE] = swing_mode
|
||||
if supported_features & SUPPORT_SWING_MODE:
|
||||
data[ATTR_SWING_MODE] = self.current_swing_mode
|
||||
if self.swing_list:
|
||||
data[ATTR_SWING_LIST] = self.swing_list
|
||||
|
||||
is_away = self.is_away_mode_on
|
||||
if is_away is not None:
|
||||
if supported_features & SUPPORT_AWAY_MODE:
|
||||
is_away = self.is_away_mode_on
|
||||
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
|
||||
|
||||
is_aux_heat = self.is_aux_heat_on
|
||||
if is_aux_heat is not None:
|
||||
if supported_features & SUPPORT_AUX_HEAT:
|
||||
is_aux_heat = self.is_aux_heat_on
|
||||
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
|
||||
|
||||
return data
|
||||
|
@ -9,35 +9,23 @@ import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_OPERATION_MODE, ATTR_FAN_MODE, ATTR_SWING_MODE,
|
||||
ATTR_CURRENT_TEMPERATURE, ClimateDevice, PLATFORM_SCHEMA,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_SWING_MODE, STATE_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL,
|
||||
STATE_DRY, STATE_FAN_ONLY
|
||||
)
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE,
|
||||
ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
ClimateDevice)
|
||||
from homeassistant.components.daikin import (
|
||||
daikin_api_setup,
|
||||
ATTR_TARGET_TEMPERATURE,
|
||||
ATTR_INSIDE_TEMPERATURE,
|
||||
ATTR_OUTSIDE_TEMPERATURE
|
||||
)
|
||||
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
|
||||
daikin_api_setup)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME,
|
||||
TEMP_CELSIUS,
|
||||
ATTR_TEMPERATURE
|
||||
)
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pydaikin==0.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_FAN_MODE |
|
||||
SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_SWING_MODE)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=None): cv.string,
|
||||
@ -56,19 +44,22 @@ HA_ATTR_TO_DAIKIN = {
|
||||
ATTR_OPERATION_MODE: 'mode',
|
||||
ATTR_FAN_MODE: 'f_rate',
|
||||
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):
|
||||
"""Setup the Daikin HVAC platform."""
|
||||
"""Set up the Daikin HVAC platform."""
|
||||
if discovery_info is not None:
|
||||
host = discovery_info.get('ip')
|
||||
name = None
|
||||
_LOGGER.info("Discovered a Daikin AC on %s", host)
|
||||
_LOGGER.debug("Discovered a Daikin AC on %s", host)
|
||||
else:
|
||||
host = config.get(CONF_HOST)
|
||||
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)
|
||||
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):
|
||||
"""Retrieve device settings from API library cache."""
|
||||
value = None
|
||||
@ -108,29 +110,34 @@ class DaikinClimate(ClimateDevice):
|
||||
|
||||
if key in [ATTR_TEMPERATURE, ATTR_INSIDE_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
|
||||
if key == ATTR_TARGET_TEMPERATURE:
|
||||
value = self._api.device.values.get('stemp')
|
||||
elif key == ATTR_TARGET_TEMPERATURE:
|
||||
value = self._api.device.values.get(daikin_attr)
|
||||
cast_to_float = True
|
||||
elif key == ATTR_OUTSIDE_TEMPERATURE:
|
||||
value = self._api.device.values.get('otemp')
|
||||
value = self._api.device.values.get(daikin_attr)
|
||||
cast_to_float = True
|
||||
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:
|
||||
value = self._api.device.represent('f_dir')[1].title()
|
||||
value = self._api.device.represent(daikin_attr)[1].title()
|
||||
elif key == ATTR_OPERATION_MODE:
|
||||
# Daikin can return also internal states auto-1 or auto-7
|
||||
# and we need to translate them as AUTO
|
||||
value = re.sub(
|
||||
'[^a-z]',
|
||||
'',
|
||||
self._api.device.represent('mode')[1]
|
||||
self._api.device.represent(daikin_attr)[1]
|
||||
).title()
|
||||
|
||||
if value is None:
|
||||
_LOGGER.warning("Invalid value requested for key %s", key)
|
||||
_LOGGER.error("Invalid value requested for key %s", key)
|
||||
else:
|
||||
if value == "-" or value == "--":
|
||||
value = None
|
||||
@ -178,7 +185,7 @@ class DaikinClimate(ClimateDevice):
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
return self._supported_features
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -7,6 +7,7 @@ https://home-assistant.io/components/demo/
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE,
|
||||
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
|
||||
|
||||
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_OPERATION_MODE | SUPPORT_AUX_HEAT |
|
||||
SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
|
@ -10,16 +10,12 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN,
|
||||
PLATFORM_SCHEMA,
|
||||
STATE_ECO, STATE_GAS, STATE_ELECTRIC,
|
||||
STATE_HEAT_PUMP, STATE_HIGH_DEMAND,
|
||||
STATE_OFF, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE,
|
||||
ClimateDevice)
|
||||
from homeassistant.const import (ATTR_ENTITY_ID,
|
||||
CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT,
|
||||
ATTR_TEMPERATURE)
|
||||
DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS,
|
||||
STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
|
||||
TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyeconet==0.0.4']
|
||||
@ -59,6 +55,7 @@ HA_STATE_TO_ECONET = {
|
||||
STATE_GAS: 'gas',
|
||||
STATE_HIGH_DEMAND: 'High Demand',
|
||||
STATE_OFF: 'Off',
|
||||
STATE_PERFORMANCE: 'Performance'
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
def service_handle(service):
|
||||
"""Handler for services."""
|
||||
"""Handle the service calls."""
|
||||
entity_ids = service.data.get('entity_id')
|
||||
all_heaters = hass.data[ECONET_DATA]['water_heaters']
|
||||
_heaters = [
|
||||
@ -105,12 +102,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
_water_heater.schedule_update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_ADD_VACATION,
|
||||
service_handle,
|
||||
hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle,
|
||||
schema=ADD_VACATION_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_DELETE_VACATION,
|
||||
service_handle,
|
||||
hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle,
|
||||
schema=DELETE_VACATION_SCHEMA)
|
||||
|
||||
|
||||
@ -138,7 +133,7 @@ class EcoNetWaterHeater(ClimateDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
vacations = self.water_heater.get_vacations()
|
||||
if vacations:
|
||||
@ -157,8 +152,7 @@ class EcoNetWaterHeater(ClimateDevice):
|
||||
"""
|
||||
Return current operation as one of the following.
|
||||
|
||||
["eco", "heat_pump",
|
||||
"high_demand", "electric_only"]
|
||||
["eco", "heat_pump", "high_demand", "electric_only"]
|
||||
"""
|
||||
current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode)
|
||||
return current_op
|
||||
@ -189,7 +183,7 @@ class EcoNetWaterHeater(ClimateDevice):
|
||||
if target_temp is not None:
|
||||
self.water_heater.set_target_set_point(target_temp)
|
||||
else:
|
||||
_LOGGER.error("A target temperature must be provided.")
|
||||
_LOGGER.error("A target temperature must be provided")
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
@ -197,7 +191,7 @@ class EcoNetWaterHeater(ClimateDevice):
|
||||
if op_mode_to_set is not None:
|
||||
self.water_heater.set_mode(op_mode_to_set)
|
||||
else:
|
||||
_LOGGER.error("An operation mode must be provided.")
|
||||
_LOGGER.error("An operation mode must be provided")
|
||||
|
||||
def add_vacation(self, start, end):
|
||||
"""Add a vacation to this water heater."""
|
||||
|
@ -9,9 +9,10 @@ from datetime import timedelta
|
||||
import voluptuous as vol
|
||||
|
||||
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 (
|
||||
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
|
||||
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyephember==0.1.1']
|
||||
@ -59,7 +60,10 @@ class EphEmberThermostat(ClimateDevice):
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_AUX_HEAT
|
||||
if self._hot_water:
|
||||
return SUPPORT_AUX_HEAT
|
||||
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -81,6 +85,14 @@ class EphEmberThermostat(ClimateDevice):
|
||||
"""Return the temperature we try to reach."""
|
||||
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
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
@ -105,17 +117,38 @@ class EphEmberThermostat(ClimateDevice):
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
return
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
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
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
return self._zone['targetTemperature']
|
||||
# Hot water temp doesn't support being changed
|
||||
if self._hot_water:
|
||||
return self._zone['targetTemperature']
|
||||
|
||||
return 5
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
return self._zone['targetTemperature']
|
||||
if self._hot_water:
|
||||
return self._zone['targetTemperature']
|
||||
|
||||
return 35
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['python-eq3bt==0.1.6']
|
||||
REQUIREMENTS = ['python-eq3bt==0.1.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -17,7 +17,8 @@ from homeassistant.components.climate import (
|
||||
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
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.event import (
|
||||
async_track_state_change, async_track_time_interval)
|
||||
@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor']
|
||||
|
||||
DEFAULT_TOLERANCE = 0.3
|
||||
DEFAULT_NAME = 'Generic Thermostat'
|
||||
DEFAULT_AWAY_TEMP = 16
|
||||
|
||||
CONF_HEATER = 'heater'
|
||||
CONF_SENSOR = 'target_sensor'
|
||||
@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
|
||||
CONF_KEEP_ALIVE = 'keep_alive'
|
||||
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
|
||||
CONF_AWAY_TEMP = 'away_temp'
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
cv.time_period, cv.positive_timedelta),
|
||||
vol.Optional(CONF_INITIAL_OPERATION_MODE):
|
||||
vol.In([STATE_AUTO, STATE_OFF]),
|
||||
vol.Optional(CONF_AWAY_TEMP,
|
||||
default=DEFAULT_AWAY_TEMP): vol.Coerce(float)
|
||||
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
|
||||
})
|
||||
|
||||
|
||||
@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice):
|
||||
self._operation_list = [STATE_HEAT, STATE_OFF]
|
||||
if initial_operation_mode == STATE_OFF:
|
||||
self._enabled = False
|
||||
self._current_operation = STATE_OFF
|
||||
else:
|
||||
self._enabled = True
|
||||
self._active = False
|
||||
@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice):
|
||||
self._max_temp = max_temp
|
||||
self._target_temp = target_temp
|
||||
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._is_away = False
|
||||
|
||||
@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice):
|
||||
async_track_time_interval(
|
||||
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
|
||||
def async_added_to_hass(self):
|
||||
"""Run when entity about to be added."""
|
||||
@ -154,19 +161,29 @@ class GenericThermostat(ClimateDevice):
|
||||
self._target_temp = self.max_temp
|
||||
else:
|
||||
self._target_temp = self.min_temp
|
||||
_LOGGER.warning('Undefined target temperature, \
|
||||
falling back to %s', self._target_temp)
|
||||
_LOGGER.warning("Undefined target temperature,"
|
||||
"falling back to %s", self._target_temp)
|
||||
else:
|
||||
self._target_temp = float(
|
||||
old_state.attributes[ATTR_TEMPERATURE])
|
||||
self._is_away = True if str(
|
||||
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False
|
||||
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
|
||||
self._current_operation = STATE_OFF
|
||||
self._enabled = False
|
||||
if self._initial_operation_mode is None:
|
||||
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
|
||||
self._enabled = False
|
||||
if old_state.attributes[ATTR_AWAY_MODE] is not None:
|
||||
self._is_away = str(
|
||||
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
|
||||
if (self._initial_operation_mode is None and
|
||||
old_state.attributes[ATTR_OPERATION_MODE] is not None):
|
||||
self._current_operation = \
|
||||
old_state.attributes[ATTR_OPERATION_MODE]
|
||||
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
|
||||
def state(self):
|
||||
@ -230,7 +247,7 @@ class GenericThermostat(ClimateDevice):
|
||||
if self._is_device_active:
|
||||
self._heater_turn_off()
|
||||
else:
|
||||
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
|
||||
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
|
||||
return
|
||||
# Ensure we updae the current operation after changing the mode
|
||||
self.schedule_update_ha_state()
|
||||
@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice):
|
||||
self._cur_temp = self.hass.config.units.temperature(
|
||||
float(state.state), unit)
|
||||
except ValueError as ex:
|
||||
_LOGGER.error('Unable to update from sensor: %s', ex)
|
||||
_LOGGER.error("Unable to update from sensor: %s", ex)
|
||||
|
||||
@callback
|
||||
def _async_control_heating(self):
|
||||
@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice):
|
||||
if not self._active and None not in (self._cur_temp,
|
||||
self._target_temp):
|
||||
self._active = True
|
||||
_LOGGER.info('Obtained current and target temperature. '
|
||||
'Generic thermostat active.')
|
||||
_LOGGER.info("Obtained current and target temperature. "
|
||||
"Generic thermostat active. %s, %s",
|
||||
self._cur_temp, self._target_temp)
|
||||
|
||||
if not self._active:
|
||||
return
|
||||
@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice):
|
||||
too_cold = self._target_temp - self._cur_temp >= \
|
||||
self._cold_tolerance
|
||||
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()
|
||||
else:
|
||||
too_hot = self._cur_temp - self._target_temp >= \
|
||||
self._hot_tolerance
|
||||
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()
|
||||
else:
|
||||
is_heating = self._is_device_active
|
||||
@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice):
|
||||
too_hot = self._cur_temp - self._target_temp >= \
|
||||
self._hot_tolerance
|
||||
if too_hot:
|
||||
_LOGGER.info('Turning off heater %s',
|
||||
_LOGGER.info("Turning off heater %s",
|
||||
self.heater_entity_id)
|
||||
self._heater_turn_off()
|
||||
else:
|
||||
too_cold = self._target_temp - self._cur_temp >= \
|
||||
self._cold_tolerance
|
||||
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()
|
||||
|
||||
@property
|
||||
@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice):
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
return self._support_flags
|
||||
|
||||
@callback
|
||||
def _heater_turn_on(self):
|
||||
|
@ -1,178 +1,178 @@
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.hive/
|
||||
"""
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
|
||||
'ON': STATE_ON, 'OFF': STATE_OFF}
|
||||
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
|
||||
STATE_ON: 'ON', STATE_OFF: 'OFF'}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AUX_HEAT)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive climate devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveClimateEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveClimateEntity(ClimateDevice):
|
||||
"""Hive Climate Device."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the Climate device."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
if self.device_type == "Heating":
|
||||
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
elif self.device_type == "HotWater":
|
||||
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the Climate device."""
|
||||
friendly_name = "Climate Device"
|
||||
if self.device_type == "Heating":
|
||||
friendly_name = "Heating"
|
||||
if self.node_name is not None:
|
||||
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
||||
elif self.device_type == "HotWater":
|
||||
friendly_name = "Hot Water"
|
||||
return friendly_name
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.current_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.get_target_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return minimum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.min_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.max_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of the operation modes."""
|
||||
return self.modes
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current mode."""
|
||||
if self.device_type == "Heating":
|
||||
currentmode = self.session.heating.get_mode(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
currentmode = self.session.hotwater.get_mode(self.node_id)
|
||||
return HIVE_TO_HASS_STATE.get(currentmode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new Heating mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_mode(self.node_id, new_mode)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.set_mode(self.node_id, new_mode)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if new_temperature is not None:
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_target_temperature(self.node_id,
|
||||
new_temperature)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if auxiliary heater is on."""
|
||||
boost_status = None
|
||||
if self.device_type == "Heating":
|
||||
boost_status = self.session.heating.get_boost(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
boost_status = self.session.hotwater.get_boost(self.node_id)
|
||||
return boost_status == "ON"
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
target_boost_time = 30
|
||||
if self.device_type == "Heating":
|
||||
curtemp = self.session.heating.current_temperature(self.node_id)
|
||||
curtemp = round(curtemp * 2) / 2
|
||||
target_boost_temperature = curtemp + 0.5
|
||||
self.session.heating.turn_boost_on(self.node_id,
|
||||
target_boost_time,
|
||||
target_boost_temperature)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_on(self.node_id,
|
||||
target_boost_time)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.turn_boost_off(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_off(self.node_id)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data frome Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.hive/
|
||||
"""
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
|
||||
'ON': STATE_ON, 'OFF': STATE_OFF}
|
||||
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
|
||||
STATE_ON: 'ON', STATE_OFF: 'OFF'}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AUX_HEAT)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive climate devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveClimateEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveClimateEntity(ClimateDevice):
|
||||
"""Hive Climate Device."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the Climate device."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
if self.device_type == "Heating":
|
||||
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
elif self.device_type == "HotWater":
|
||||
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the Climate device."""
|
||||
friendly_name = "Climate Device"
|
||||
if self.device_type == "Heating":
|
||||
friendly_name = "Heating"
|
||||
if self.node_name is not None:
|
||||
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
||||
elif self.device_type == "HotWater":
|
||||
friendly_name = "Hot Water"
|
||||
return friendly_name
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.current_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.get_target_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return minimum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.min_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.max_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of the operation modes."""
|
||||
return self.modes
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current mode."""
|
||||
if self.device_type == "Heating":
|
||||
currentmode = self.session.heating.get_mode(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
currentmode = self.session.hotwater.get_mode(self.node_id)
|
||||
return HIVE_TO_HASS_STATE.get(currentmode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new Heating mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_mode(self.node_id, new_mode)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.set_mode(self.node_id, new_mode)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if new_temperature is not None:
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_target_temperature(self.node_id,
|
||||
new_temperature)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if auxiliary heater is on."""
|
||||
boost_status = None
|
||||
if self.device_type == "Heating":
|
||||
boost_status = self.session.heating.get_boost(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
boost_status = self.session.hotwater.get_boost(self.node_id)
|
||||
return boost_status == "ON"
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
target_boost_time = 30
|
||||
if self.device_type == "Heating":
|
||||
curtemp = self.session.heating.current_temperature(self.node_id)
|
||||
curtemp = round(curtemp * 2) / 2
|
||||
target_boost_temperature = curtemp + 0.5
|
||||
self.session.heating.turn_boost_on(self.node_id,
|
||||
target_boost_time,
|
||||
target_boost_temperature)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_on(self.node_id,
|
||||
target_boost_time)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.turn_boost_off(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_off(self.node_id)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data frome Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
|
@ -5,13 +5,14 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.knx/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
|
||||
from homeassistant.components.climate import (
|
||||
PLATFORM_SCHEMA, ClimateDevice, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
ClimateDevice)
|
||||
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
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@ -61,24 +62,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
@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 climate(s) for KNX platform."""
|
||||
if DATA_KNX not in hass.data \
|
||||
or not hass.data[DATA_KNX].initialized:
|
||||
return False
|
||||
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
|
||||
return
|
||||
|
||||
if discovery_info is not None:
|
||||
async_add_devices_discovery(hass, discovery_info, async_add_devices)
|
||||
else:
|
||||
async_add_devices_config(hass, config, async_add_devices)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
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 = []
|
||||
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||
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
|
||||
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
|
||||
|
||||
climate = xknx.devices.Climate(
|
||||
hass.data[DATA_KNX].xknx,
|
||||
name=config.get(CONF_NAME),
|
||||
group_address_temperature=config.get(
|
||||
CONF_TEMPERATURE_ADDRESS),
|
||||
group_address_temperature=config.get(CONF_TEMPERATURE_ADDRESS),
|
||||
group_address_target_temperature=config.get(
|
||||
CONF_TARGET_TEMPERATURE_ADDRESS),
|
||||
group_address_setpoint_shift=config.get(
|
||||
CONF_SETPOINT_SHIFT_ADDRESS),
|
||||
group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
|
||||
group_address_setpoint_shift_state=config.get(
|
||||
CONF_SETPOINT_SHIFT_STATE_ADDRESS),
|
||||
setpoint_shift_step=config.get(
|
||||
CONF_SETPOINT_SHIFT_STEP),
|
||||
setpoint_shift_max=config.get(
|
||||
CONF_SETPOINT_SHIFT_MAX),
|
||||
setpoint_shift_min=config.get(
|
||||
CONF_SETPOINT_SHIFT_MIN),
|
||||
group_address_operation_mode=config.get(
|
||||
CONF_OPERATION_MODE_ADDRESS),
|
||||
setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP),
|
||||
setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX),
|
||||
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(
|
||||
CONF_OPERATION_MODE_STATE_ADDRESS),
|
||||
group_address_controller_status=config.get(
|
||||
@ -127,10 +118,10 @@ def async_add_devices_config(hass, config, async_add_devices):
|
||||
|
||||
|
||||
class KNXClimate(ClimateDevice):
|
||||
"""Representation of a KNX climate."""
|
||||
"""Representation of a KNX climate device."""
|
||||
|
||||
def __init__(self, hass, device):
|
||||
"""Initialization of KNXClimate."""
|
||||
"""Initialize of a KNX climate device."""
|
||||
self.device = device
|
||||
self.hass = hass
|
||||
self.async_register_callbacks()
|
||||
@ -149,7 +140,7 @@ class KNXClimate(ClimateDevice):
|
||||
"""Register callbacks to update hass after device was changed."""
|
||||
@asyncio.coroutine
|
||||
def after_update_callback(device):
|
||||
"""Callback after device was updated."""
|
||||
"""Call after device was updated."""
|
||||
# pylint: disable=unused-argument
|
||||
yield from self.async_update_ha_state()
|
||||
self.device.register_device_updated_cb(after_update_callback)
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.components.climate import (
|
||||
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
|
||||
SUPPORT_AUX_HEAT)
|
||||
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 (
|
||||
CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE,
|
||||
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_STATE_TOPIC = 'power_state_topic'
|
||||
CONF_POWER_STATE_TEMPLATE = 'power_state_template'
|
||||
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
|
||||
CONF_MODE_STATE_TOPIC = 'mode_state_topic'
|
||||
CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
|
||||
CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_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_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_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_STATE_TOPIC = 'away_mode_state_topic'
|
||||
CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template'
|
||||
CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
|
||||
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
|
||||
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
|
||||
CONF_AUX_COMMAND_TOPIC = 'aux_command_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_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_HOLD_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_MODE_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_HOLD_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):
|
||||
mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_FAN_MODE_LIST,
|
||||
@ -100,6 +122,26 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""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([
|
||||
MqttClimate(
|
||||
hass,
|
||||
@ -125,6 +167,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
CONF_CURRENT_TEMPERATURE_TOPIC
|
||||
)
|
||||
},
|
||||
value_templates,
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_RETAIN),
|
||||
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):
|
||||
"""Representation of a demo climate device."""
|
||||
|
||||
def __init__(self, hass, name, topic, qos, retain, mode_list,
|
||||
fan_mode_list, swing_mode_list, target_temperature, away,
|
||||
hold, current_fan_mode, current_swing_mode,
|
||||
current_operation, aux, send_if_off, payload_on,
|
||||
payload_off, availability_topic, payload_available,
|
||||
payload_not_available):
|
||||
def __init__(self, hass, name, topic, value_templates, qos, retain,
|
||||
mode_list, fan_mode_list, swing_mode_list,
|
||||
target_temperature, away, hold, current_fan_mode,
|
||||
current_swing_mode, current_operation, aux, send_if_off,
|
||||
payload_on, payload_off, availability_topic,
|
||||
payload_available, payload_not_available):
|
||||
"""Initialize the climate device."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self._topic = topic
|
||||
self._value_templates = value_templates
|
||||
self._qos = qos
|
||||
self._retain = retain
|
||||
self._target_temperature = target_temperature
|
||||
@ -184,6 +228,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_current_temp_received(topic, payload, qos):
|
||||
"""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:
|
||||
self._current_temperature = float(payload)
|
||||
self.async_schedule_update_ha_state()
|
||||
@ -198,6 +247,10 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_mode_received(topic, payload, qos):
|
||||
"""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:
|
||||
_LOGGER.error("Invalid mode: %s", payload)
|
||||
else:
|
||||
@ -212,6 +265,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_temperature_received(topic, payload, qos):
|
||||
"""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:
|
||||
self._target_temperature = float(payload)
|
||||
self.async_schedule_update_ha_state()
|
||||
@ -226,6 +284,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_fan_mode_received(topic, payload, qos):
|
||||
"""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:
|
||||
_LOGGER.error("Invalid fan mode: %s", payload)
|
||||
else:
|
||||
@ -240,6 +303,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_swing_mode_received(topic, payload, qos):
|
||||
"""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:
|
||||
_LOGGER.error("Invalid swing mode: %s", payload)
|
||||
else:
|
||||
@ -254,6 +322,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_away_mode_received(topic, payload, qos):
|
||||
"""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:
|
||||
self._away = True
|
||||
elif payload == self._payload_off:
|
||||
@ -271,6 +348,14 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_aux_mode_received(topic, payload, qos):
|
||||
"""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:
|
||||
self._aux = True
|
||||
elif payload == self._payload_off:
|
||||
@ -288,6 +373,10 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@callback
|
||||
def handle_hold_mode_received(topic, payload, qos):
|
||||
"""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.async_schedule_update_ha_state()
|
||||
|
||||
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/climate.mysensors/
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
|
||||
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE)
|
||||
STATE_COOL, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
|
||||
ClimateDevice)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
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):
|
||||
"""Setup the mysensors climate."""
|
||||
"""Set up the MySensors climate."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices)
|
||||
|
||||
@ -52,8 +53,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return (TEMP_CELSIUS
|
||||
if self.gateway.metric else TEMP_FAHRENHEIT)
|
||||
return TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
@ -139,7 +139,8 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, value_type, value)
|
||||
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.schedule_update_ha_state()
|
||||
|
||||
@ -149,7 +150,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
|
||||
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.schedule_update_ha_state()
|
||||
|
||||
@ -159,7 +160,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
self.node_id, self.child_id, self.value_type,
|
||||
DICT_HA_TO_MYS[operation_mode])
|
||||
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.schedule_update_ha_state()
|
||||
|
||||
|
@ -10,7 +10,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components.nest import DATA_NEST
|
||||
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,
|
||||
ATTR_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
|
||||
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)),
|
||||
})
|
||||
|
||||
STATE_ECO = 'eco'
|
||||
STATE_HEAT_COOL = 'heat-cool'
|
||||
NEST_MODE_HEAT_COOL = 'heat-cool'
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
|
||||
@ -118,14 +117,14 @@ class NestThermostat(ClimateDevice):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
|
||||
return self._mode
|
||||
elif self._mode == STATE_HEAT_COOL:
|
||||
elif self._mode == NEST_MODE_HEAT_COOL:
|
||||
return STATE_AUTO
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""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 None
|
||||
|
||||
@ -136,7 +135,7 @@ class NestThermostat(ClimateDevice):
|
||||
self._eco_temperature[0]:
|
||||
# eco_temperature is always a low, high tuple
|
||||
return self._eco_temperature[0]
|
||||
if self._mode == STATE_HEAT_COOL:
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return self._target_temperature[0]
|
||||
return None
|
||||
|
||||
@ -147,7 +146,7 @@ class NestThermostat(ClimateDevice):
|
||||
self._eco_temperature[1]:
|
||||
# eco_temperature is always a low, high tuple
|
||||
return self._eco_temperature[1]
|
||||
if self._mode == STATE_HEAT_COOL:
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return self._target_temperature[1]
|
||||
return None
|
||||
|
||||
@ -160,7 +159,7 @@ class NestThermostat(ClimateDevice):
|
||||
"""Set new target temperature."""
|
||||
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
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:
|
||||
temp = (target_temp_low, target_temp_high)
|
||||
else:
|
||||
@ -173,7 +172,7 @@ class NestThermostat(ClimateDevice):
|
||||
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
|
||||
device_mode = operation_mode
|
||||
elif operation_mode == STATE_AUTO:
|
||||
device_mode = STATE_HEAT_COOL
|
||||
device_mode = NEST_MODE_HEAT_COOL
|
||||
self.device.mode = device_mode
|
||||
|
||||
@property
|
||||
|
@ -294,14 +294,14 @@ class SensiboClimate(ClimateDevice):
|
||||
self._id, 'swing', swing_mode, self._ac_states)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_on(self):
|
||||
def async_turn_on(self):
|
||||
"""Turn Sensibo unit on."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
yield from self._client.async_set_ac_state_property(
|
||||
self._id, 'on', True, self._ac_states)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_off(self):
|
||||
def async_turn_off(self):
|
||||
"""Turn Sensibo unit on."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
yield from self._client.async_set_ac_state_property(
|
||||
|
@ -6,13 +6,13 @@ https://home-assistant.io/components/climate.tesla/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||
ENTITY_ID_FORMAT, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
ClimateDevice)
|
||||
from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN
|
||||
from homeassistant.components.tesla import TeslaDevice
|
||||
from homeassistant.const import (
|
||||
TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE)
|
||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -60,7 +60,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
|
||||
return OPERATION_LIST
|
||||
|
||||
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)
|
||||
self.tesla_device.update()
|
||||
self._target_temperature = self.tesla_device.get_goal_temp()
|
||||
|
@ -7,25 +7,25 @@ Eneco.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.toon/
|
||||
"""
|
||||
import homeassistant.components.toon as toon_main
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, ATTR_TEMPERATURE, STATE_PERFORMANCE, STATE_HEAT, STATE_ECO,
|
||||
STATE_COOL, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_PERFORMANCE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
|
||||
import homeassistant.components.toon as toon_main
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class ThermostatDevice(ClimateDevice):
|
||||
"""Interface class for the toon module and HA."""
|
||||
"""Representation of a Toon climate device."""
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Initialize the device."""
|
||||
"""Initialize the Toon climate device."""
|
||||
self._name = 'Toon van Eneco'
|
||||
self.hass = hass
|
||||
self.thermos = hass.data[toon_main.TOON_HANDLE]
|
||||
@ -47,12 +47,12 @@ class ThermostatDevice(ClimateDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of this Thermostat."""
|
||||
"""Return the name of this thermostat."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""The unit of measurement used by the platform."""
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
@ -63,7 +63,7 @@ class ThermostatDevice(ClimateDevice):
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
"""Return a list of available operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
@ -82,7 +82,7 @@ class ThermostatDevice(ClimateDevice):
|
||||
self.thermos.set_temp(temp)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new operation mode as toonlib requires it."""
|
||||
"""Set new operation mode."""
|
||||
toonlib_values = {
|
||||
STATE_PERFORMANCE: 'Comfort',
|
||||
STATE_HEAT: 'Home',
|
||||
|
267
homeassistant/components/climate/venstar.py
Normal file
267
homeassistant/components/climate/venstar.py
Normal 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")
|
@ -8,16 +8,16 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC,
|
||||
STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND,
|
||||
STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY,
|
||||
ATTR_TARGET_TEMP_HIGH, ClimateDevice, SUPPORT_TARGET_TEMPERATURE,
|
||||
ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_ELECTRIC,
|
||||
STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, STATE_HIGH_DEMAND,
|
||||
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_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_AUX_HEAT)
|
||||
ClimateDevice)
|
||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -30,6 +30,8 @@ ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
|
||||
ATTR_SMART_TEMPERATURE = 'smart_temperature'
|
||||
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
|
||||
ATTR_VACATION_MODE = 'vacation_mode'
|
||||
ATTR_HEAT_ON = 'heat_on'
|
||||
ATTR_COOL_ON = 'cool_on'
|
||||
|
||||
DEPENDENCIES = ['wink']
|
||||
|
||||
@ -93,7 +95,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
||||
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
|
||||
@property
|
||||
@ -104,7 +106,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
target_temp_high = self.target_temperature_high
|
||||
target_temp_low = self.target_temperature_low
|
||||
@ -131,6 +133,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
||||
if 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
|
||||
if current_humidity is not None:
|
||||
data[ATTR_CURRENT_HUMIDITY] = current_humidity
|
||||
@ -174,6 +182,16 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
||||
"""Return status of if the thermostat has detected occupancy."""
|
||||
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
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
@ -385,7 +403,7 @@ class WinkAC(WinkDevice, ClimateDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
target_temp_high = self.target_temperature_high
|
||||
target_temp_low = self.target_temperature_low
|
||||
@ -508,7 +526,7 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
|
||||
data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()
|
||||
|
@ -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
|
||||
from datetime import datetime
|
||||
import json
|
||||
@ -26,18 +31,18 @@ REQUIREMENTS = ['warrant==0.6.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ALEXA = 'alexa'
|
||||
CONF_GOOGLE_ACTIONS = 'google_actions'
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_ALIASES = 'aliases'
|
||||
CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
|
||||
CONF_ENTITY_CONFIG = 'entity_config'
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_GOOGLE_ACTIONS = 'google_actions'
|
||||
CONF_RELAYER = 'relayer'
|
||||
CONF_USER_POOL_ID = 'user_pool_id'
|
||||
CONF_ALIASES = 'aliases'
|
||||
|
||||
MODE_DEV = 'development'
|
||||
DEFAULT_MODE = 'production'
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
CONF_ENTITY_CONFIG = 'entity_config'
|
||||
MODE_DEV = 'development'
|
||||
|
||||
ALEXA_ENTITY_SCHEMA = vol.Schema({
|
||||
vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string,
|
||||
@ -149,7 +154,7 @@ class Cloud:
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
@ -195,8 +200,8 @@ class Cloud:
|
||||
if not jwt_success:
|
||||
return False
|
||||
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
|
||||
self._start_cloud)
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, self._start_cloud)
|
||||
|
||||
return True
|
||||
|
||||
@ -248,7 +253,7 @@ class Cloud:
|
||||
for token in 'id_token', 'access_token':
|
||||
self._decode_claims(info[token])
|
||||
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
|
||||
|
||||
self.id_token = info['id_token']
|
||||
@ -282,15 +287,15 @@ class Cloud:
|
||||
header = jwt.get_unverified_header(token)
|
||||
except jose_exceptions.JWTError as err:
|
||||
raise ValueError(str(err)) from None
|
||||
kid = header.get("kid")
|
||||
kid = header.get('kid')
|
||||
|
||||
if kid is None:
|
||||
raise ValueError('No kid in header')
|
||||
raise ValueError("No kid in header")
|
||||
|
||||
# Locate the key for this kid
|
||||
key = None
|
||||
for key_dict in self.jwt_keyset["keys"]:
|
||||
if key_dict["kid"] == kid:
|
||||
for key_dict in self.jwt_keyset['keys']:
|
||||
if key_dict['kid'] == kid:
|
||||
key = key_dict
|
||||
break
|
||||
if not key:
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Package to communicate with the authentication API."""
|
||||
import logging
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -22,7 +21,7 @@ class UserNotConfirmed(CloudError):
|
||||
|
||||
|
||||
class ExpiredCode(CloudError):
|
||||
"""Raised when an expired code is encoutered."""
|
||||
"""Raised when an expired code is encountered."""
|
||||
|
||||
|
||||
class InvalidCode(CloudError):
|
||||
@ -38,7 +37,7 @@ class PasswordChangeRequired(CloudError):
|
||||
|
||||
|
||||
class UnknownError(CloudError):
|
||||
"""Raised when an unknown error occurrs."""
|
||||
"""Raised when an unknown error occurs."""
|
||||
|
||||
|
||||
AWS_EXCEPTIONS = {
|
||||
@ -98,7 +97,7 @@ def resend_email_confirm(cloud, email):
|
||||
|
||||
|
||||
def forgot_password(cloud, email):
|
||||
"""Initiate forgotten password flow."""
|
||||
"""Initialize forgotten password flow."""
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
cognito = _cognito(cloud, username=email)
|
||||
|
@ -3,8 +3,8 @@ import asyncio
|
||||
from functools import wraps
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import (
|
||||
HomeAssistantView, RequestDataValidator)
|
||||
@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass):
|
||||
"""Initialize the HTTP api."""
|
||||
"""Initialize the HTTP API."""
|
||||
hass.http.register_view(CloudLoginView)
|
||||
hass.http.register_view(CloudLogoutView)
|
||||
hass.http.register_view(CloudAccountView)
|
||||
@ -40,7 +40,7 @@ _CLOUD_ERRORS = {
|
||||
|
||||
|
||||
def _handle_cloud_errors(handler):
|
||||
"""Helper method to handle auth errors."""
|
||||
"""Handle auth errors."""
|
||||
@asyncio.coroutine
|
||||
@wraps(handler)
|
||||
def error_handler(view, request, *args, **kwargs):
|
||||
|
@ -12,7 +12,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from . import auth_api
|
||||
from .const import MESSAGE_EXPIRATION
|
||||
|
||||
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -85,7 +84,7 @@ class CloudIoT:
|
||||
})
|
||||
self.tries = 0
|
||||
|
||||
_LOGGER.info('Connected')
|
||||
_LOGGER.info("Connected")
|
||||
self.state = STATE_CONNECTED
|
||||
|
||||
while not client.closed:
|
||||
@ -107,7 +106,7 @@ class CloudIoT:
|
||||
disconnect_warn = 'Received invalid JSON.'
|
||||
break
|
||||
|
||||
_LOGGER.debug('Received message: %s', msg)
|
||||
_LOGGER.debug("Received message: %s", msg)
|
||||
|
||||
response = {
|
||||
'msgid': msg['msgid'],
|
||||
@ -126,14 +125,14 @@ class CloudIoT:
|
||||
response['error'] = 'unknown-handler'
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception('Error handling message')
|
||||
_LOGGER.exception("Error handling message")
|
||||
response['error'] = 'exception'
|
||||
|
||||
_LOGGER.debug('Publishing message: %s', response)
|
||||
_LOGGER.debug("Publishing message: %s", response)
|
||||
yield from client.send_json(response)
|
||||
|
||||
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:
|
||||
if err.code == 401:
|
||||
@ -141,18 +140,18 @@ class CloudIoT:
|
||||
self.close_requested = True
|
||||
# Should we notify user?
|
||||
else:
|
||||
_LOGGER.warning('Unable to connect: %s', err)
|
||||
_LOGGER.warning("Unable to connect: %s", 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
|
||||
if not self.close_requested:
|
||||
_LOGGER.exception('Unexpected error')
|
||||
_LOGGER.exception("Unexpected error")
|
||||
|
||||
finally:
|
||||
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:
|
||||
remove_hass_stop_listener()
|
||||
@ -169,7 +168,7 @@ class CloudIoT:
|
||||
self.tries += 1
|
||||
|
||||
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(
|
||||
min(30, (self.tries - 1) * 5), loop=hass.loop))
|
||||
yield from self.retry_task
|
||||
@ -205,8 +204,8 @@ def async_handle_message(hass, cloud, handler_name, payload):
|
||||
@asyncio.coroutine
|
||||
def async_handle_alexa(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for Alexa."""
|
||||
result = yield from alexa.async_handle_message(hass, cloud.alexa_config,
|
||||
payload)
|
||||
result = yield from alexa.async_handle_message(
|
||||
hass, cloud.alexa_config, payload)
|
||||
return result
|
||||
|
||||
|
||||
@ -214,8 +213,8 @@ def async_handle_alexa(hass, cloud, payload):
|
||||
@asyncio.coroutine
|
||||
def async_handle_google_actions(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for Google Actions."""
|
||||
result = yield from ga.async_handle_message(hass, cloud.gactions_config,
|
||||
payload)
|
||||
result = yield from ga.async_handle_message(
|
||||
hass, cloud.gactions_config, payload)
|
||||
return result
|
||||
|
||||
|
||||
@ -227,9 +226,9 @@ def async_handle_cloud(hass, cloud, payload):
|
||||
|
||||
if action == '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'])
|
||||
else:
|
||||
_LOGGER.warning('Received unknown cloud action: %s', action)
|
||||
_LOGGER.warning("Received unknown cloud action: %s", action)
|
||||
|
||||
return None
|
||||
|
@ -8,11 +8,11 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import (discovery)
|
||||
from homeassistant.helpers.dispatcher import (dispatcher_send)
|
||||
CONF_HOST, CONF_NAME, CONF_PIN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
REQUIREMENTS = ['pycomfoconnect==0.3']
|
||||
|
||||
@ -115,7 +115,7 @@ class ComfoConnectBridge(object):
|
||||
self.comfoconnect.disconnect()
|
||||
|
||||
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)
|
||||
|
||||
from pycomfoconnect import (
|
||||
|
@ -5,9 +5,11 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.homematic/
|
||||
"""
|
||||
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.const import STATE_UNKNOWN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -69,3 +71,40 @@ class HMCover(HMDevice, CoverDevice):
|
||||
"""Generate a data dictoinary (self._data) from metadata."""
|
||||
self._state = "LEVEL"
|
||||
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)
|
||||
|
@ -5,17 +5,18 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.knx/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
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 (
|
||||
CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE,
|
||||
SUPPORT_SET_POSITION, SUPPORT_STOP, SUPPORT_SET_TILT_POSITION,
|
||||
ATTR_POSITION, ATTR_TILT_POSITION)
|
||||
from homeassistant.core import callback
|
||||
ATTR_POSITION, ATTR_TILT_POSITION, PLATFORM_SCHEMA, SUPPORT_CLOSE,
|
||||
SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION,
|
||||
SUPPORT_STOP, CoverDevice)
|
||||
from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
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_SHORT_ADDRESS = 'move_short_address'
|
||||
@ -50,20 +51,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
@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 cover(s) for KNX platform."""
|
||||
if DATA_KNX not in hass.data \
|
||||
or not hass.data[DATA_KNX].initialized:
|
||||
return False
|
||||
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
|
||||
return
|
||||
|
||||
if discovery_info is not None:
|
||||
async_add_devices_discovery(hass, discovery_info, async_add_devices)
|
||||
else:
|
||||
async_add_devices_config(hass, config, async_add_devices)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
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."""
|
||||
@asyncio.coroutine
|
||||
def after_update_callback(device):
|
||||
"""Callback after device was updated."""
|
||||
"""Call after device was updated."""
|
||||
# pylint: disable=unused-argument
|
||||
yield from self.async_update_ha_state()
|
||||
self.device.register_device_updated_cb(after_update_callback)
|
||||
@ -209,7 +206,7 @@ class KNXCover(CoverDevice):
|
||||
|
||||
@callback
|
||||
def auto_updater_hook(self, now):
|
||||
"""Callback for autoupdater."""
|
||||
"""Call for the autoupdater."""
|
||||
# pylint: disable=unused-argument
|
||||
self.async_schedule_update_ha_state()
|
||||
if self.device.position_reached():
|
||||
|
76
homeassistant/components/cover/lutron.py
Normal file
76
homeassistant/components/cover/lutron.py
Normal 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
|
@ -5,12 +5,12 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.mysensors/
|
||||
"""
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
|
||||
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(
|
||||
hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices)
|
||||
|
||||
|
@ -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."""
|
||||
import RFXtrx as rfxtrxmod
|
||||
|
||||
covers = rfxtrx.get_devices_from_config(config, RfxtrxCover)
|
||||
add_devices_callback(covers)
|
||||
add_devices(covers)
|
||||
|
||||
def cover_update(event):
|
||||
"""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)
|
||||
if new_device:
|
||||
add_devices_callback([new_device])
|
||||
add_devices([new_device])
|
||||
|
||||
rfxtrx.apply_received_command(event)
|
||||
|
||||
@ -59,7 +59,7 @@ class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling available in RFXtrx cover."""
|
||||
"""Return the polling state. No polling available in RFXtrx cover."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/cover.tahoma/
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
from homeassistant.components.tahoma import (
|
||||
DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
|
||||
|
||||
@ -30,11 +30,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class TahomaCover(TahomaDevice, CoverDevice):
|
||||
"""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):
|
||||
"""Update method."""
|
||||
self.controller.get_states([self.tahoma_device])
|
||||
@ -46,12 +41,16 @@ class TahomaCover(TahomaDevice, CoverDevice):
|
||||
|
||||
0 is closed, 100 is fully open.
|
||||
"""
|
||||
position = 100 - self.tahoma_device.active_states['core:ClosureState']
|
||||
if position <= 5:
|
||||
return 0
|
||||
if position >= 95:
|
||||
return 100
|
||||
return position
|
||||
try:
|
||||
position = 100 - \
|
||||
self.tahoma_device.active_states['core:ClosureState']
|
||||
if position <= 5:
|
||||
return 0
|
||||
if position >= 95:
|
||||
return 100
|
||||
return position
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def set_cover_position(self, position, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
@ -63,6 +62,14 @@ class TahomaCover(TahomaDevice, CoverDevice):
|
||||
if self.current_cover_position is not None:
|
||||
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):
|
||||
"""Open the cover."""
|
||||
self.apply_action('open')
|
||||
@ -78,10 +85,3 @@ class TahomaCover(TahomaDevice, CoverDevice):
|
||||
self.apply_action('setPosition', 'secured')
|
||||
else:
|
||||
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
|
||||
|
@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.wink/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN
|
||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['wink']
|
||||
@ -31,25 +31,28 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
|
||||
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Close the shade."""
|
||||
"""Close the cover."""
|
||||
self.wink.set_state(0)
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
"""Open the shade."""
|
||||
"""Open the cover."""
|
||||
self.wink.set_state(1)
|
||||
|
||||
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)
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of roller shutter."""
|
||||
return int(self.wink.state()*100)
|
||||
"""Return the current position of cover shutter."""
|
||||
if self.wink.state() is not None:
|
||||
return int(self.wink.state()*100)
|
||||
else:
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
|
@ -59,7 +59,7 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
|
||||
"""Move the cover to a specific 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."""
|
||||
if ATTR_CURTAIN_LEVEL in data:
|
||||
self._pos = int(data[ATTR_CURTAIN_LEVEL])
|
||||
|
@ -5,11 +5,12 @@ For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/datadog/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_PREFIX,
|
||||
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED,
|
||||
STATE_UNKNOWN)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_LOGBOOK_ENTRY,
|
||||
EVENT_STATE_CHANGED, STATE_UNKNOWN)
|
||||
from homeassistant.helpers import state as state_helper
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@ -36,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the Datadog component."""
|
||||
"""Set up the Datadog component."""
|
||||
from datadog import initialize, statsd
|
||||
|
||||
conf = config[DOMAIN]
|
||||
@ -81,36 +82,19 @@ def setup(hass, config):
|
||||
if isinstance(value, (float, int)):
|
||||
attribute = "{}.{}".format(metric, key.replace(' ', '_'))
|
||||
statsd.gauge(
|
||||
attribute,
|
||||
value,
|
||||
sample_rate=sample_rate,
|
||||
tags=tags
|
||||
)
|
||||
attribute, value, sample_rate=sample_rate, tags=tags)
|
||||
|
||||
_LOGGER.debug(
|
||||
'Sent metric %s: %s (tags: %s)',
|
||||
attribute,
|
||||
value,
|
||||
tags
|
||||
)
|
||||
"Sent metric %s: %s (tags: %s)", attribute, value, tags)
|
||||
|
||||
try:
|
||||
value = state_helper.state_as_number(state)
|
||||
except ValueError:
|
||||
_LOGGER.debug(
|
||||
'Error sending %s: %s (tags: %s)',
|
||||
metric,
|
||||
state.state,
|
||||
tags
|
||||
)
|
||||
"Error sending %s: %s (tags: %s)", metric, state.state, tags)
|
||||
return
|
||||
|
||||
statsd.gauge(
|
||||
metric,
|
||||
value,
|
||||
sample_rate=sample_rate,
|
||||
tags=tags
|
||||
)
|
||||
statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags)
|
||||
|
||||
_LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags)
|
||||
|
||||
|
@ -4,20 +4,20 @@ Support for deCONZ devices.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/deconz/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.discovery import SERVICE_DECONZ
|
||||
from homeassistant.const import (
|
||||
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 discovery
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
REQUIREMENTS = ['pydeconz==23']
|
||||
REQUIREMENTS = ['pydeconz==25']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -27,8 +27,8 @@ CONFIG_FILE = 'deconz.conf'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_PORT, default=80): cv.port,
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
@ -53,14 +53,14 @@ Unlock your deCONZ gateway to register with Home Assistant.
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Setup services and configuration for deCONZ component."""
|
||||
"""Set up services and configuration for deCONZ component."""
|
||||
result = False
|
||||
config_file = yield from hass.async_add_job(
|
||||
load_json, hass.config.path(CONFIG_FILE))
|
||||
|
||||
@asyncio.coroutine
|
||||
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[CONF_HOST] = discovery_info.get(CONF_HOST)
|
||||
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
|
||||
@ -85,17 +85,18 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
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.
|
||||
Start websocket for push notification of state changes from deCONZ.
|
||||
"""
|
||||
_LOGGER.debug("deCONZ config %s", deconz_config)
|
||||
from pydeconz import DeconzSession
|
||||
websession = async_get_clientsession(hass)
|
||||
deconz = DeconzSession(hass.loop, websession, **deconz_config)
|
||||
result = yield from deconz.async_load_parameters()
|
||||
if result is False:
|
||||
_LOGGER.error("Failed to communicate with deCONZ.")
|
||||
_LOGGER.error("Failed to communicate with deCONZ")
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = deconz
|
||||
@ -125,8 +126,7 @@ def async_setup_deconz(hass, config, deconz_config):
|
||||
data = call.data.get(SERVICE_DATA)
|
||||
yield from deconz.async_put_state(field, data)
|
||||
hass.services.async_register(
|
||||
DOMAIN, 'configure', async_configure,
|
||||
schema=SERVICE_SCHEMA)
|
||||
DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz.close)
|
||||
return True
|
||||
@ -146,9 +146,8 @@ def async_request_configuration(hass, config, deconz_config):
|
||||
deconz_config[CONF_API_KEY] = api_key
|
||||
result = yield from async_setup_deconz(hass, config, deconz_config)
|
||||
if result:
|
||||
yield from hass.async_add_job(save_json,
|
||||
hass.config.path(CONFIG_FILE),
|
||||
deconz_config)
|
||||
yield from hass.async_add_job(
|
||||
save_json, hass.config.path(CONFIG_FILE), deconz_config)
|
||||
configurator.async_request_done(request_id)
|
||||
return
|
||||
else:
|
||||
|
@ -25,9 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_PUB_KEY = 'pub_key'
|
||||
CONF_SSH_KEY = 'ssh_key'
|
||||
|
||||
DEFAULT_SSH_PORT = 22
|
||||
|
||||
SECRET_GROUP = 'Password or SSH Key'
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
@ -118,20 +116,10 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
self.port = config[CONF_PORT]
|
||||
|
||||
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.host, self.port, self.username, self.password,
|
||||
self.ssh_key, self.mode == 'ap')
|
||||
else:
|
||||
if not self.password:
|
||||
_LOGGER.error("No password specified")
|
||||
self.success_init = False
|
||||
return
|
||||
|
||||
self.connection = TelnetConnection(
|
||||
self.host, self.port, self.username, self.password,
|
||||
self.mode == 'ap')
|
||||
@ -177,11 +165,16 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
"""
|
||||
devices = {}
|
||||
devices.update(self._get_wl())
|
||||
devices = self._get_arp(devices)
|
||||
devices = self._get_neigh(devices)
|
||||
devices.update(self._get_arp())
|
||||
devices.update(self._get_neigh(devices))
|
||||
if not self.mode == 'ap':
|
||||
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):
|
||||
lines = self.connection.run_command(_WL_CMD)
|
||||
@ -219,18 +212,13 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
result = _parse_lines(lines, _IP_NEIGH_REGEX)
|
||||
devices = {}
|
||||
for device in result:
|
||||
if device['mac']:
|
||||
if device['mac'] is not None:
|
||||
mac = device['mac'].upper()
|
||||
devices[mac] = Device(mac, None, None)
|
||||
else:
|
||||
cur_devices = {
|
||||
k: v for k, v in
|
||||
cur_devices.items() if v.ip != device['ip']
|
||||
}
|
||||
cur_devices.update(devices)
|
||||
return cur_devices
|
||||
old_ip = cur_devices.get(mac, {}).ip or None
|
||||
devices[mac] = Device(mac, device.get('ip', old_ip), None)
|
||||
return devices
|
||||
|
||||
def _get_arp(self, cur_devices):
|
||||
def _get_arp(self):
|
||||
lines = self.connection.run_command(_ARP_CMD)
|
||||
if not lines:
|
||||
return {}
|
||||
@ -240,13 +228,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
if device['mac']:
|
||||
mac = device['mac'].upper()
|
||||
devices[mac] = Device(mac, device['ip'], None)
|
||||
else:
|
||||
cur_devices = {
|
||||
k: v for k, v in
|
||||
cur_devices.items() if v.ip != device['ip']
|
||||
}
|
||||
cur_devices.update(devices)
|
||||
return cur_devices
|
||||
return devices
|
||||
|
||||
|
||||
class _Connection:
|
||||
@ -272,7 +254,7 @@ class SshConnection(_Connection):
|
||||
|
||||
def __init__(self, host, port, username, password, ssh_key, ap):
|
||||
"""Initialize the SSH connection properties."""
|
||||
super(SshConnection, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
self._ssh = None
|
||||
self._host = host
|
||||
@ -322,7 +304,7 @@ class SshConnection(_Connection):
|
||||
self._ssh.login(self._host, self._username,
|
||||
password=self._password, port=self._port)
|
||||
|
||||
super(SshConnection, self).connect()
|
||||
super().connect()
|
||||
|
||||
def disconnect(self): \
|
||||
# pylint: disable=broad-except
|
||||
@ -334,7 +316,7 @@ class SshConnection(_Connection):
|
||||
finally:
|
||||
self._ssh = None
|
||||
|
||||
super(SshConnection, self).disconnect()
|
||||
super().disconnect()
|
||||
|
||||
|
||||
class TelnetConnection(_Connection):
|
||||
@ -342,7 +324,7 @@ class TelnetConnection(_Connection):
|
||||
|
||||
def __init__(self, host, port, username, password, ap):
|
||||
"""Initialize the Telnet connection properties."""
|
||||
super(TelnetConnection, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
self._telnet = None
|
||||
self._host = host
|
||||
@ -361,7 +343,6 @@ class TelnetConnection(_Connection):
|
||||
try:
|
||||
if not self.connected:
|
||||
self.connect()
|
||||
|
||||
self._telnet.write('{}\n'.format(command).encode('ascii'))
|
||||
data = (self._telnet.read_until(self._prompt_string).
|
||||
split(b'\n')[1:-1])
|
||||
@ -392,7 +373,7 @@ class TelnetConnection(_Connection):
|
||||
self._telnet.write((self._password + '\n').encode('ascii'))
|
||||
self._prompt_string = self._telnet.read_until(b'#').split(b'\n')[-1]
|
||||
|
||||
super(TelnetConnection, self).connect()
|
||||
super().connect()
|
||||
|
||||
def disconnect(self): \
|
||||
# pylint: disable=broad-except
|
||||
@ -402,4 +383,4 @@ class TelnetConnection(_Connection):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
super(TelnetConnection, self).disconnect()
|
||||
super().disconnect()
|
||||
|
@ -14,8 +14,8 @@ from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_tracker import (
|
||||
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
|
||||
ATTR_GPS, ATTR_GPS_ACCURACY)
|
||||
ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_HOST_NAME,
|
||||
ATTR_MAC, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
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
|
||||
|
||||
REQUIREMENTS = ['aioautomatic==0.6.4']
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
_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'
|
||||
|
||||
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
|
||||
|
||||
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_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({
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_SECRET): cv.string,
|
||||
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
|
||||
vol.Optional(CONF_DEVICES, default=None): vol.All(
|
||||
cv.ensure_list, [cv.string])
|
||||
vol.Optional(CONF_DEVICES, default=None):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
})
|
||||
|
||||
|
||||
@ -142,7 +140,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
|
||||
@asyncio.coroutine
|
||||
def initialize_callback(code, state):
|
||||
"""Callback after OAuth2 response is returned."""
|
||||
"""Call after OAuth2 response is returned."""
|
||||
try:
|
||||
session = yield from client.create_session_from_oauth_code(
|
||||
code, state)
|
||||
@ -181,12 +179,12 @@ class AutomaticAuthCallbackView(HomeAssistantView):
|
||||
return response
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Error authorizing Automatic. Invalid response returned.")
|
||||
"Error authorizing Automatic. Invalid response returned")
|
||||
return response
|
||||
|
||||
if DATA_CONFIGURING not in hass.data or \
|
||||
params['state'] not in hass.data[DATA_CONFIGURING]:
|
||||
_LOGGER.error("Automatic configuration request not found.")
|
||||
_LOGGER.error("Automatic configuration request not found")
|
||||
return response
|
||||
|
||||
code = params['code']
|
||||
@ -220,16 +218,15 @@ class AutomaticData(object):
|
||||
|
||||
@asyncio.coroutine
|
||||
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
|
||||
|
||||
# Fire a hass event
|
||||
self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data)
|
||||
|
||||
if event.vehicle.id not in self.vehicle_info:
|
||||
# If vehicle hasn't been seen yet, request the detailed
|
||||
# info for this vehicle.
|
||||
_LOGGER.info("New vehicle found.")
|
||||
_LOGGER.info("New vehicle found")
|
||||
try:
|
||||
vehicle = yield from event.get_vehicle()
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
@ -240,7 +237,7 @@ class AutomaticData(object):
|
||||
if event.created_at < self.vehicle_seen[event.vehicle.id]:
|
||||
# Skip events received out of order
|
||||
_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])
|
||||
return
|
||||
self.vehicle_seen[event.vehicle.id] = event.created_at
|
||||
@ -270,13 +267,13 @@ class AutomaticData(object):
|
||||
self.ws_close_requested = False
|
||||
|
||||
if self.ws_reconnect_handle is not None:
|
||||
_LOGGER.debug("Retrying websocket connection.")
|
||||
_LOGGER.debug("Retrying websocket connection")
|
||||
try:
|
||||
ws_loop_future = yield from self.client.ws_connect()
|
||||
except aioautomatic.exceptions.UnauthorizedClientError:
|
||||
_LOGGER.error("Client unauthorized for websocket connection. "
|
||||
"Ensure Websocket is selected in the Automatic "
|
||||
"developer application event delivery preferences.")
|
||||
"developer application event delivery preferences")
|
||||
return
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
if self.ws_reconnect_handle is None:
|
||||
@ -290,14 +287,14 @@ class AutomaticData(object):
|
||||
self.ws_reconnect_handle()
|
||||
self.ws_reconnect_handle = None
|
||||
|
||||
_LOGGER.info("Websocket connected.")
|
||||
_LOGGER.info("Websocket connected")
|
||||
|
||||
try:
|
||||
yield from ws_loop_future
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
_LOGGER.error(str(err))
|
||||
|
||||
_LOGGER.info("Websocket closed.")
|
||||
_LOGGER.info("Websocket closed")
|
||||
|
||||
# If websocket was close was not requested, attempt to reconnect
|
||||
if not self.ws_close_requested:
|
||||
|
@ -15,7 +15,10 @@ import voluptuous as vol
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
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.core import callback
|
||||
from homeassistant.util import slugify, decorator
|
||||
@ -140,6 +143,11 @@ def _parse_see_args(message, subscribe_topic):
|
||||
kwargs['attributes']['tid'] = message['tid']
|
||||
if 'addr' in message:
|
||||
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
|
||||
|
||||
|
@ -14,7 +14,9 @@ import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.device_tracker import (
|
||||
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'
|
||||
|
||||
@ -22,6 +24,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
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_USERNAME): cv.string,
|
||||
vol.Required(CONF_HTTP_ID): cv.string
|
||||
@ -39,16 +45,23 @@ class TomatoDeviceScanner(DeviceScanner):
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
|
||||
port = config[CONF_PORT]
|
||||
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(
|
||||
'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'},
|
||||
auth=requests.auth.HTTPBasicAuth(username, password)).prepare()
|
||||
|
||||
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.success_init = self._update_tomato_info()
|
||||
@ -74,10 +87,16 @@ class TomatoDeviceScanner(DeviceScanner):
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
self.logger.info("Scanning")
|
||||
_LOGGER.info("Scanning")
|
||||
|
||||
try:
|
||||
response = requests.Session().send(self.req, timeout=3)
|
||||
if self.ssl:
|
||||
response = requests.Session().send(self.req,
|
||||
timeout=3,
|
||||
verify=self.verify_ssl)
|
||||
else:
|
||||
response = requests.Session().send(self.req, timeout=3)
|
||||
|
||||
# Calling and parsing the Tomato api here. We only need the
|
||||
# wldev and dhcpd_lease values.
|
||||
if response.status_code == 200:
|
||||
@ -92,7 +111,7 @@ class TomatoDeviceScanner(DeviceScanner):
|
||||
|
||||
elif response.status_code == 401:
|
||||
# Authentication error
|
||||
self.logger.exception((
|
||||
_LOGGER.exception((
|
||||
"Failed to authenticate, "
|
||||
"please check your username and password"))
|
||||
return False
|
||||
@ -100,17 +119,17 @@ class TomatoDeviceScanner(DeviceScanner):
|
||||
except requests.exceptions.ConnectionError:
|
||||
# We get this if we could not connect to the router or
|
||||
# an invalid http_id was supplied.
|
||||
self.logger.exception("Failed to connect to the router or "
|
||||
"invalid http_id supplied")
|
||||
_LOGGER.exception("Failed to connect to the router or "
|
||||
"invalid http_id supplied")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
# We get this if we could not connect to the router or
|
||||
# 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
|
||||
|
||||
except ValueError:
|
||||
# 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
|
||||
|
@ -11,11 +11,11 @@ import re
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.device_tracker import (
|
||||
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -30,8 +30,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_DHCP_SOFTWARE,
|
||||
default=DEFAULT_DHCP_SOFTWARE): vol.In(DHCP_SOFTWARES)
|
||||
vol.Optional(CONF_DHCP_SOFTWARE, default=DEFAULT_DHCP_SOFTWARE):
|
||||
vol.In(DHCP_SOFTWARES),
|
||||
})
|
||||
|
||||
|
||||
@ -49,14 +49,14 @@ def get_scanner(hass, config):
|
||||
def _refresh_on_acccess_denied(func):
|
||||
"""If remove rebooted, it lost our session so rebuld one and try again."""
|
||||
def decorator(self, *args, **kwargs):
|
||||
"""Wrapper function to refresh session_id on PermissionError."""
|
||||
"""Wrap the function to refresh session_id on PermissionError."""
|
||||
try:
|
||||
return func(self, *args, **kwargs)
|
||||
except PermissionError:
|
||||
_LOGGER.warning("Invalid session detected." +
|
||||
" Tryign to refresh session_id and re-run the rpc")
|
||||
self.session_id = _get_session_id(self.url, self.username,
|
||||
self.password)
|
||||
" Trying to refresh session_id and re-run RPC")
|
||||
self.session_id = _get_session_id(
|
||||
self.url, self.username, self.password)
|
||||
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
@ -80,8 +80,8 @@ class UbusDeviceScanner(DeviceScanner):
|
||||
self.last_results = {}
|
||||
self.url = 'http://{}/ubus'.format(host)
|
||||
|
||||
self.session_id = _get_session_id(self.url, self.username,
|
||||
self.password)
|
||||
self.session_id = _get_session_id(
|
||||
self.url, self.username, self.password)
|
||||
self.hostapd = []
|
||||
self.mac2name = None
|
||||
self.success_init = self.session_id is not None
|
||||
|
@ -98,11 +98,15 @@ class UnifiDeviceScanner(DeviceScanner):
|
||||
self.connected = False
|
||||
|
||||
def _get_update(self):
|
||||
from pexpect import pxssh
|
||||
from pexpect import pxssh, exceptions
|
||||
|
||||
try:
|
||||
if not self.connected:
|
||||
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.prompt()
|
||||
return self.ssh.before
|
||||
@ -110,7 +114,7 @@ class UnifiDeviceScanner(DeviceScanner):
|
||||
_LOGGER.error("Unexpected SSH error: %s", str(err))
|
||||
self._disconnect()
|
||||
return None
|
||||
except AssertionError as err:
|
||||
except (AssertionError, exceptions.EOF) as err:
|
||||
_LOGGER.error("Connection to AP unavailable: %s", str(err))
|
||||
self._disconnect()
|
||||
return None
|
||||
|
@ -84,7 +84,7 @@ class UPCDeviceScanner(DeviceScanner):
|
||||
|
||||
@asyncio.coroutine
|
||||
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
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -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
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['netdisco==1.2.3']
|
||||
REQUIREMENTS = ['netdisco==1.2.4']
|
||||
|
||||
DOMAIN = 'discovery'
|
||||
|
||||
@ -53,6 +53,7 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_TELLDUSLIVE: ('tellduslive', None),
|
||||
SERVICE_HUE: ('hue', None),
|
||||
SERVICE_DECONZ: ('deconz', None),
|
||||
SERVICE_DAIKIN: ('daikin', None),
|
||||
'google_cast': ('media_player', 'cast'),
|
||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||
'plex_mediaserver': ('media_player', 'plex'),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user