Merge pull request #11153 from home-assistant/release-0-60

0.60
This commit is contained in:
Fabian Affolter 2017-12-17 15:44:41 +01:00 committed by GitHub
commit 79240a0d47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 7221 additions and 1011 deletions

View file

@ -11,6 +11,9 @@ omit =
homeassistant/components/abode.py
homeassistant/components/*/abode.py
homeassistant/components/ads/__init__.py
homeassistant/components/*/ads.py
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
@ -85,7 +88,7 @@ omit =
homeassistant/components/hive.py
homeassistant/components/*/hive.py
homeassistant/components/homematic.py
homeassistant/components/homematic/__init__.py
homeassistant/components/*/homematic.py
homeassistant/components/insteon_local.py
@ -261,8 +264,10 @@ omit =
homeassistant/components/*/zoneminder.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/egardia.py
homeassistant/components/alarm_control_panel/ialarm.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
@ -279,14 +284,16 @@ omit =
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/browser.py
homeassistant/components/calendar/caldav.py
homeassistant/components/calendar/todoist.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/canary.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/onvif.py
homeassistant/components/camera/ring.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/ephember.py
@ -294,6 +301,7 @@ omit =
homeassistant/components/climate/flexit.py
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
homeassistant/components/climate/honeywell.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/oem.py
homeassistant/components/climate/proliphix.py
@ -331,10 +339,10 @@ omit =
homeassistant/components/device_tracker/sky_hub.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/device_tracker/swisscom.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tado.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tile.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
@ -352,8 +360,8 @@ omit =
homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/light/avion.py
homeassistant/components/light/blinkt.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/blinkt.py
homeassistant/components/light/decora.py
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/flux_led.py
@ -364,8 +372,8 @@ omit =
homeassistant/components/light/limitlessled.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/rpi_gpio_pwm.py
homeassistant/components/light/piglow.py
homeassistant/components/light/rpi_gpio_pwm.py
homeassistant/components/light/sensehat.py
homeassistant/components/light/tikteck.py
homeassistant/components/light/tplink.py
@ -376,9 +384,9 @@ omit =
homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/nello.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
homeassistant/components/media_extractor.py
homeassistant/components/media_player/anthemav.py
@ -420,11 +428,13 @@ omit =
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/ue_smart_radio.py
homeassistant/components/media_player/vizio.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/media_player/yamaha_musiccast.py
homeassistant/components/media_player/ziggo_mediabox_xl.py
homeassistant/components/mycroft.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
@ -472,6 +482,7 @@ omit =
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/airvisual.py
homeassistant/components/sensor/alpha_vantage.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
@ -482,8 +493,8 @@ omit =
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/citybikes.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/citybikes.py
homeassistant/components/sensor/comed_hourly_pricing.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/crimereports.py
@ -511,6 +522,7 @@ omit =
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py
homeassistant/components/sensor/geizhals.py
homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py
@ -621,8 +633,8 @@ omit =
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/telnet.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/xiaomi_miio.py
homeassistant/components/telegram_bot/*
@ -631,7 +643,9 @@ omit =
homeassistant/components/tts/baidu.py
homeassistant/components/tts/microsoft.py
homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/mqtt.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/vacuum/xiaomi_miio.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/metoffice.py
@ -640,7 +654,6 @@ omit =
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
homeassistant/components/zwave/util.py
homeassistant/components/vacuum/mqtt.py
[report]
# Regexes for lines to exclude from consideration

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
# Ensure Docker script files uses LF to support Docker for Windows.
setup_docker_prereqs eol=lf
/virtualization/Docker/scripts/* eol=lf

View file

@ -54,6 +54,7 @@ homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen
homeassistant/components/sensor/sytadin.py @gautric

View file

@ -230,7 +230,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None:
def cmdline() -> List[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith(os.path.sep + '__main__.py'):
if os.path.basename(sys.argv[0]) == '__main__.py':
modulepath = os.path.dirname(sys.argv[0])
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if

View file

@ -0,0 +1,217 @@
"""
ADS Component.
For more details about this component, please refer to the documentation.
https://home-assistant.io/components/ads/
"""
import os
import threading
import struct
import logging
import ctypes
from collections import namedtuple
import voluptuous as vol
from homeassistant.const import CONF_DEVICE, CONF_PORT, CONF_IP_ADDRESS, \
EVENT_HOMEASSISTANT_STOP
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyads==2.2.6']
_LOGGER = logging.getLogger(__name__)
DATA_ADS = 'data_ads'
# Supported Types
ADSTYPE_INT = 'int'
ADSTYPE_UINT = 'uint'
ADSTYPE_BYTE = 'byte'
ADSTYPE_BOOL = 'bool'
DOMAIN = 'ads'
# config variable names
CONF_ADS_VAR = 'adsvar'
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
CONF_ADS_TYPE = 'adstype'
CONF_ADS_FACTOR = 'factor'
CONF_ADS_VALUE = 'value'
SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICE): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Optional(CONF_IP_ADDRESS): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
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
})
def setup(hass, config):
"""Set up the ADS component."""
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,
ADSTYPE_INT: pyads.PLCTYPE_INT,
ADSTYPE_UINT: pyads.PLCTYPE_UINT,
}
AdsHub.PLCTYPE_BOOL = pyads.PLCTYPE_BOOL
AdsHub.PLCTYPE_BYTE = pyads.PLCTYPE_BYTE
AdsHub.PLCTYPE_INT = pyads.PLCTYPE_INT
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
)
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)
def handle_write_data_by_name(call):
"""Write a value to the connected ADS device."""
ads_var = call.data.get(CONF_ADS_VAR)
ads_type = call.data.get(CONF_ADS_TYPE)
value = call.data.get(CONF_ADS_VALUE)
try:
ads.write_by_name(ads_var, value, ads.ADS_TYPEMAP[ads_type])
except pyads.ADSError as err:
_LOGGER.error(err)
# load descriptions from services.yaml
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(
DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name,
descriptions[SERVICE_WRITE_DATA_BY_NAME],
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME
)
return True
# tuple to hold data needed for notification
NotificationItem = namedtuple(
'NotificationItem', 'hnotify huser name plc_datatype callback'
)
class AdsHub:
"""Representation of a PyADS connection."""
def __init__(self, ads_client):
"""Initialize the ADS Hub."""
self._client = ads_client
self._client.open()
# 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')
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
)
self._client.close()
def register_device(self, device):
"""Register a new device."""
self._devices.append(device)
def write_by_name(self, name, value, plc_datatype):
"""Write a value to the device."""
with self._lock:
return self._client.write_by_name(name, value, plc_datatype)
def read_by_name(self, name, plc_datatype):
"""Read a value from the device."""
with self._lock:
return self._client.read_by_name(name, plc_datatype)
def add_device_notification(self, name, plc_datatype, callback):
"""Add a notification to the ADS devices."""
from pyads import NotificationAttrib
attr = NotificationAttrib(ctypes.sizeof(plc_datatype))
with self._lock:
hnotify, huser = self._client.add_device_notification(
name, attr, self._device_notification_callback
)
hnotify = int(hnotify)
_LOGGER.debug(
'Added Device Notification %d for variable %s', hnotify, name
)
self._notification_items[hnotify] = NotificationItem(
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)
data = contents.data
try:
notification_item = self._notification_items[hnotify]
except KeyError:
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify)
return
# 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:
value = struct.unpack('<h', bytearray(data)[:2])[0]
elif notification_item.plc_datatype == self.PLCTYPE_BYTE:
value = struct.unpack('<B', bytearray(data)[:1])[0]
elif notification_item.plc_datatype == self.PLCTYPE_UINT:
value = struct.unpack('<H', bytearray(data)[:2])[0]
else:
value = bytearray(data)
_LOGGER.warning('No callback available for this datatype.')
# execute callback
notification_item.callback(notification_item.name, value)

View file

@ -0,0 +1,15 @@
# Describes the format for available ADS services
write_data_by_name:
description: Write a value to the connected ADS device.
fields:
adsvar:
description: The name of the variable to write to.
example: '.global_var'
adstype:
description: The data type of the variable to write to.
example: 'int'
value:
description: The value to write to the variable.
example: 1

View file

@ -7,30 +7,21 @@ https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarmdecoder import (DATA_AD,
SIGNAL_PANEL_MESSAGE)
from homeassistant.components.alarmdecoder import (
DATA_AD, SIGNAL_PANEL_MESSAGE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)
STATE_ALARM_TRIGGERED)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['alarmdecoder']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up for AlarmDecoder alarm panels."""
_LOGGER.debug("AlarmDecoderAlarmPanel: setup")
device = AlarmDecoderAlarmPanel("Alarm Panel", hass)
async_add_devices([device])
add_devices([AlarmDecoderAlarmPanel()])
return True
@ -38,38 +29,35 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel."""
def __init__(self, name, hass):
def __init__(self):
"""Initialize the alarm panel."""
self._display = ""
self._name = name
self._state = STATE_UNKNOWN
_LOGGER.debug("Setting up panel")
self._name = "Alarm Panel"
self._state = None
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_PANEL_MESSAGE, self._message_callback)
@callback
def _message_callback(self, message):
if message.alarm_sounding or message.fire_alarm:
if self._state != STATE_ALARM_TRIGGERED:
self._state = STATE_ALARM_TRIGGERED
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
elif message.armed_away:
if self._state != STATE_ALARM_ARMED_AWAY:
self._state = STATE_ALARM_ARMED_AWAY
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
elif message.armed_home:
if self._state != STATE_ALARM_ARMED_HOME:
self._state = STATE_ALARM_ARMED_HOME
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
else:
if self._state != STATE_ALARM_DISARMED:
self._state = STATE_ALARM_DISARMED
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
@property
def name(self):
@ -91,26 +79,20 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
"""Return the state of the device."""
return self._state
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
def alarm_disarm(self, code=None):
"""Send disarm command."""
_LOGGER.debug("alarm_disarm: %s", code)
if code:
_LOGGER.debug("alarm_disarm: sending %s1", str(code))
self.hass.data[DATA_AD].send("{!s}1".format(code))
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
def alarm_arm_away(self, code=None):
"""Send arm away command."""
_LOGGER.debug("alarm_arm_away: %s", code)
if code:
_LOGGER.debug("alarm_arm_away: sending %s2", str(code))
self.hass.data[DATA_AD].send("{!s}2".format(code))
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
def alarm_arm_home(self, code=None):
"""Send arm home command."""
_LOGGER.debug("alarm_arm_home: %s", code)
if code:
_LOGGER.debug("alarm_arm_home: sending %s3", str(code))
self.hass.data[DATA_AD].send("{!s}3".format(code))

View file

@ -0,0 +1,92 @@
"""
Support for Canary alarm.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.canary/
"""
import logging
from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.components.canary import DATA_CANARY
from homeassistant.const import STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, \
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_HOME
DEPENDENCIES = ['canary']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Canary alarms."""
data = hass.data[DATA_CANARY]
devices = []
for location in data.locations:
devices.append(CanaryAlarm(data, location.location_id))
add_devices(devices, True)
class CanaryAlarm(AlarmControlPanel):
"""Representation of a Canary alarm control panel."""
def __init__(self, data, location_id):
"""Initialize a Canary security camera."""
self._data = data
self._location_id = location_id
@property
def name(self):
"""Return the name of the alarm."""
location = self._data.get_location(self._location_id)
return location.name
@property
def state(self):
"""Return the state of the device."""
from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, \
LOCATION_MODE_NIGHT
location = self._data.get_location(self._location_id)
if location.is_private:
return STATE_ALARM_DISARMED
mode = location.mode
if mode.name == LOCATION_MODE_AWAY:
return STATE_ALARM_ARMED_AWAY
elif mode.name == LOCATION_MODE_HOME:
return STATE_ALARM_ARMED_HOME
elif mode.name == LOCATION_MODE_NIGHT:
return STATE_ALARM_ARMED_NIGHT
else:
return None
@property
def device_state_attributes(self):
"""Return the state attributes."""
location = self._data.get_location(self._location_id)
return {
'private': location.is_private
}
def alarm_disarm(self, code=None):
"""Send disarm command."""
location = self._data.get_location(self._location_id)
self._data.set_location_mode(self._location_id, location.mode.name,
True)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
from canary.api import LOCATION_MODE_HOME
self._data.set_location_mode(self._location_id, LOCATION_MODE_HOME)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
from canary.api import LOCATION_MODE_AWAY
self._data.set_location_mode(self._location_id, LOCATION_MODE_AWAY)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
from canary.api import LOCATION_MODE_NIGHT
self._data.set_location_mode(self._location_id, LOCATION_MODE_NIGHT)

View file

@ -4,30 +4,45 @@ Demo platform that has two fake alarm control panels.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import datetime
import homeassistant.components.alarm_control_panel.manual as manual
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_TRIGGERED, CONF_PENDING_TIME)
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, CONF_DELAY_TIME,
CONF_PENDING_TIME, CONF_TRIGGER_TIME)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo alarm control panel platform."""
add_devices([
manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10, False, {
manual.ManualAlarm(hass, 'Alarm', '1234', None, False, {
STATE_ALARM_ARMED_AWAY: {
CONF_PENDING_TIME: 5
CONF_DELAY_TIME: datetime.timedelta(seconds=0),
CONF_PENDING_TIME: datetime.timedelta(seconds=5),
CONF_TRIGGER_TIME: datetime.timedelta(seconds=10),
},
STATE_ALARM_ARMED_HOME: {
CONF_PENDING_TIME: 5
CONF_DELAY_TIME: datetime.timedelta(seconds=0),
CONF_PENDING_TIME: datetime.timedelta(seconds=5),
CONF_TRIGGER_TIME: datetime.timedelta(seconds=10),
},
STATE_ALARM_ARMED_NIGHT: {
CONF_PENDING_TIME: 5
CONF_DELAY_TIME: datetime.timedelta(seconds=0),
CONF_PENDING_TIME: datetime.timedelta(seconds=5),
CONF_TRIGGER_TIME: datetime.timedelta(seconds=10),
},
STATE_ALARM_DISARMED: {
CONF_DELAY_TIME: datetime.timedelta(seconds=0),
CONF_TRIGGER_TIME: datetime.timedelta(seconds=10),
},
STATE_ALARM_ARMED_CUSTOM_BYPASS: {
CONF_PENDING_TIME: 5
CONF_DELAY_TIME: datetime.timedelta(seconds=0),
CONF_PENDING_TIME: datetime.timedelta(seconds=5),
CONF_TRIGGER_TIME: datetime.timedelta(seconds=10),
},
STATE_ALARM_TRIGGERED: {
CONF_PENDING_TIME: 5
CONF_PENDING_TIME: datetime.timedelta(seconds=5),
},
}),
])

View file

@ -116,12 +116,20 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
"""Return the state of the device."""
return self._status
@property
def should_poll(self):
"""Poll if no report server is enabled."""
if not self._rs_enabled:
return True
return False
def handle_system_status_event(self, event):
"""Handle egardia_system_status_event."""
if event.data.get('status') is not None:
statuscode = event.data.get('status')
status = self.lookupstatusfromcode(statuscode)
self.parsestatus(status)
self.schedule_update_ha_state()
def listen_to_system_status(self):
"""Subscribe to egardia_system_status event."""
@ -161,9 +169,8 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
def update(self):
"""Update the alarm status."""
if not self._rs_enabled:
status = self._egardiasystem.getstate()
self.parsestatus(status)
status = self._egardiasystem.getstate()
self.parsestatus(status)
def alarm_disarm(self, code=None):
"""Send disarm command."""

View file

@ -0,0 +1,107 @@
"""
Interfaces with iAlarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ialarm/
"""
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)
REQUIREMENTS = ['pyialarm==0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'iAlarm'
def no_application_protocol(value):
"""Validate that value is without the application protocol."""
protocol_separator = "://"
if not value or protocol_separator in value:
raise vol.Invalid(
'Invalid host, {} is not allowed'.format(protocol_separator))
return value
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
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,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an iAlarm control panel."""
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
url = 'http://{}'.format(host)
ialarm = IAlarmPanel(name, username, password, url)
add_devices([ialarm], True)
class IAlarmPanel(alarm.AlarmControlPanel):
"""Represent an iAlarm status."""
def __init__(self, name, username, password, url):
"""Initialize the iAlarm status."""
from pyialarm import IAlarm
self._name = name
self._username = username
self._password = password
self._url = url
self._state = None
self._client = IAlarm(username, password, url)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Return the state of the device."""
status = self._client.get_status()
_LOGGER.debug('iAlarm status: %s', status)
if status:
status = int(status)
if status == self._client.DISARMED:
state = STATE_ALARM_DISARMED
elif status == self._client.ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_STAY:
state = STATE_ALARM_ARMED_HOME
else:
state = None
self._state = state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay()

View file

@ -16,24 +16,40 @@ 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_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
CONF_CODE_TEMPLATE = 'code_template'
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
DEFAULT_DELAY_TIME = datetime.timedelta(seconds=0)
DEFAULT_PENDING_TIME = datetime.timedelta(seconds=60)
DEFAULT_TRIGGER_TIME = datetime.timedelta(seconds=120)
DEFAULT_DISARM_AFTER_TRIGGER = False
SUPPORTED_PENDING_STATES = [STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED,
STATE_ALARM_ARMED_CUSTOM_BYPASS]
SUPPORTED_STATES = [STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_TRIGGERED]
SUPPORTED_PRETRIGGER_STATES = [state for state in SUPPORTED_STATES
if state != STATE_ALARM_TRIGGERED]
SUPPORTED_PENDING_STATES = [state for state in SUPPORTED_STATES
if state != STATE_ALARM_DISARMED]
ATTR_PRE_PENDING_STATE = 'pre_pending_state'
ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config):
config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]:
config[state][CONF_DELAY_TIME] = config[CONF_DELAY_TIME]
if CONF_TRIGGER_TIME not in config[state]:
config[state][CONF_TRIGGER_TIME] = config[CONF_TRIGGER_TIME]
for state in SUPPORTED_PENDING_STATES:
if CONF_PENDING_TIME not in config[state]:
config[state][CONF_PENDING_TIME] = config[CONF_PENDING_TIME]
@ -41,28 +57,44 @@ def _state_validator(config):
return config
STATE_SETTING_SCHEMA = vol.Schema({
vol.Optional(CONF_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0))
})
def _state_schema(state):
schema = {}
if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
schema[vol.Optional(CONF_TRIGGER_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
if state in SUPPORTED_PENDING_STATES:
schema[vol.Optional(CONF_PENDING_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
return vol.Schema(schema)
PLATFORM_SCHEMA = vol.Schema(vol.All({
vol.Required(CONF_PLATFORM): 'manual',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Exclusive(CONF_CODE, 'code validation'): cv.string,
vol.Exclusive(CONF_CODE_TEMPLATE, 'code validation'): cv.template,
vol.Optional(CONF_DELAY_TIME, default=DEFAULT_DELAY_TIME):
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_HOME, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_CUSTOM_BYPASS,
default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_TRIGGERED, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}):
_state_schema(STATE_ALARM_ARMED_AWAY),
vol.Optional(STATE_ALARM_ARMED_HOME, default={}):
_state_schema(STATE_ALARM_ARMED_HOME),
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}):
_state_schema(STATE_ALARM_ARMED_NIGHT),
vol.Optional(STATE_ALARM_ARMED_CUSTOM_BYPASS, default={}):
_state_schema(STATE_ALARM_ARMED_CUSTOM_BYPASS),
vol.Optional(STATE_ALARM_DISARMED, default={}):
_state_schema(STATE_ALARM_DISARMED),
vol.Optional(STATE_ALARM_TRIGGERED, default={}):
_state_schema(STATE_ALARM_TRIGGERED),
}, _state_validator))
_LOGGER = logging.getLogger(__name__)
@ -74,8 +106,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass,
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_PENDING_TIME, DEFAULT_PENDING_TIME),
config.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME),
config.get(CONF_CODE_TEMPLATE),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
config
)])
@ -86,27 +117,37 @@ class ManualAlarm(alarm.AlarmControlPanel):
Representation of an alarm status.
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to the previous state
or disarm if `disarm_after_trigger` is true.
When triggered, will be pending for the triggering state's 'delay_time'
plus the triggered state's 'pending_time'.
After that will be triggered for 'trigger_time', after that we return to
the previous state or disarm if `disarm_after_trigger` is true.
A trigger_time of zero disables the alarm_trigger service.
"""
def __init__(self, hass, name, code, pending_time, trigger_time,
def __init__(self, hass, name, code, code_template,
disarm_after_trigger, config):
"""Init the manual alarm panel."""
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._trigger_time = datetime.timedelta(seconds=trigger_time)
if code_template:
self._code = code_template
self._code.hass = hass
else:
self._code = code or None
self._disarm_after_trigger = disarm_after_trigger
self._pre_trigger_state = self._state
self._previous_state = self._state
self._state_ts = None
self._pending_time_by_state = {}
for state in SUPPORTED_PENDING_STATES:
self._pending_time_by_state[state] = datetime.timedelta(
seconds=config[state][CONF_PENDING_TIME])
self._delay_time_by_state = {
state: config[state][CONF_DELAY_TIME]
for state in SUPPORTED_PRETRIGGER_STATES}
self._trigger_time_by_state = {
state: config[state][CONF_TRIGGER_TIME]
for state in SUPPORTED_PRETRIGGER_STATES}
self._pending_time_by_state = {
state: config[state][CONF_PENDING_TIME]
for state in SUPPORTED_PENDING_STATES}
@property
def should_poll(self):
@ -121,15 +162,16 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state == STATE_ALARM_TRIGGERED:
if self._within_pending_time(self._state):
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time_by_state[self._state] +
self._trigger_time) < dt_util.utcnow():
trigger_time = self._trigger_time_by_state[self._previous_state]
if (self._state_ts + self._pending_time(self._state) +
trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
self._state = self._pre_trigger_state
self._state = self._previous_state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \
@ -138,9 +180,21 @@ class ManualAlarm(alarm.AlarmControlPanel):
return self._state
def _within_pending_time(self, state):
@property
def _active_state(self):
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
def _pending_time(self, state):
pending_time = self._pending_time_by_state[state]
return self._state_ts + pending_time > dt_util.utcnow()
if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state]
return pending_time
def _within_pending_time(self, state):
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property
def code_format(self):
@ -185,26 +239,35 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_ARMED_CUSTOM_BYPASS)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
"""
Send alarm trigger command.
No code needed, a trigger time of zero for the current state
disables the alarm.
"""
if not self._trigger_time_by_state[self._active_state]:
return
self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state):
if self._state == state:
return
self._previous_state = self._state
self._state = state
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED and self._trigger_time:
pending_time = self._pending_time(state)
if state == STATE_ALARM_TRIGGERED:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + pending_time)
trigger_time = self._trigger_time_by_state[self._previous_state]
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._trigger_time + pending_time)
self._state_ts + pending_time + trigger_time)
elif state in SUPPORTED_PENDING_STATES and pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
@ -212,7 +275,14 @@ class ManualAlarm(alarm.AlarmControlPanel):
def _validate_code(self, code, state):
"""Validate given code."""
check = self._code is None or code == self._code
if self._code is None:
return True
if isinstance(self._code, str):
alarm_code = self._code
else:
alarm_code = self._code.render(from_state=self._state,
to_state=state)
check = not alarm_code or code == alarm_code
if not check:
_LOGGER.warning("Invalid code given for %s", state)
return check
@ -223,6 +293,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
state_attr = {}
if self.state == STATE_ALARM_PENDING:
state_attr[ATTR_PRE_PENDING_STATE] = self._previous_state
state_attr[ATTR_POST_PENDING_STATE] = self._state
return state_attr

View file

@ -16,8 +16,8 @@ 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_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_DELAY_TIME, CONF_PENDING_TIME,
CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
import homeassistant.components.mqtt as mqtt
from homeassistant.helpers.event import async_track_state_change
@ -26,28 +26,44 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
CONF_CODE_TEMPLATE = 'code_template'
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_PAYLOAD_ARM_NIGHT = 'payload_arm_night'
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
DEFAULT_DELAY_TIME = datetime.timedelta(seconds=0)
DEFAULT_PENDING_TIME = datetime.timedelta(seconds=60)
DEFAULT_TRIGGER_TIME = datetime.timedelta(seconds=120)
DEFAULT_DISARM_AFTER_TRIGGER = False
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
DEFAULT_ARM_NIGHT = 'ARM_NIGHT'
DEFAULT_DISARM = 'DISARM'
SUPPORTED_PENDING_STATES = [STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED]
SUPPORTED_STATES = [STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_TRIGGERED]
SUPPORTED_PRETRIGGER_STATES = [state for state in SUPPORTED_STATES
if state != STATE_ALARM_TRIGGERED]
SUPPORTED_PENDING_STATES = [state for state in SUPPORTED_STATES
if state != STATE_ALARM_DISARMED]
ATTR_PRE_PENDING_STATE = 'pre_pending_state'
ATTR_POST_PENDING_STATE = 'post_pending_state'
def _state_validator(config):
config = copy.deepcopy(config)
for state in SUPPORTED_PRETRIGGER_STATES:
if CONF_DELAY_TIME not in config[state]:
config[state][CONF_DELAY_TIME] = config[CONF_DELAY_TIME]
if CONF_TRIGGER_TIME not in config[state]:
config[state][CONF_TRIGGER_TIME] = config[CONF_TRIGGER_TIME]
for state in SUPPORTED_PENDING_STATES:
if CONF_PENDING_TIME not in config[state]:
config[state][CONF_PENDING_TIME] = config[CONF_PENDING_TIME]
@ -55,27 +71,44 @@ def _state_validator(config):
return config
STATE_SETTING_SCHEMA = vol.Schema({
vol.Optional(CONF_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0))
})
def _state_schema(state):
schema = {}
if state in SUPPORTED_PRETRIGGER_STATES:
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
schema[vol.Optional(CONF_TRIGGER_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
if state in SUPPORTED_PENDING_STATES:
schema[vol.Optional(CONF_PENDING_TIME)] = vol.All(
cv.time_period, cv.positive_timedelta)
return vol.Schema(schema)
DEPENDENCIES = ['mqtt']
PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_PLATFORM): 'manual_mqtt',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Exclusive(CONF_CODE, 'code validation'): cv.string,
vol.Exclusive(CONF_CODE_TEMPLATE, 'code validation'): cv.template,
vol.Optional(CONF_DELAY_TIME, default=DEFAULT_DELAY_TIME):
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_HOME, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_TRIGGERED, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}):
_state_schema(STATE_ALARM_ARMED_AWAY),
vol.Optional(STATE_ALARM_ARMED_HOME, default={}):
_state_schema(STATE_ALARM_ARMED_HOME),
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}):
_state_schema(STATE_ALARM_ARMED_NIGHT),
vol.Optional(STATE_ALARM_DISARMED, default={}):
_state_schema(STATE_ALARM_DISARMED),
vol.Optional(STATE_ALARM_TRIGGERED, default={}):
_state_schema(STATE_ALARM_TRIGGERED),
vol.Required(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Required(mqtt.CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
@ -93,8 +126,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hass,
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_PENDING_TIME, DEFAULT_PENDING_TIME),
config.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME),
config.get(CONF_CODE_TEMPLATE),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
config.get(mqtt.CONF_STATE_TOPIC),
config.get(mqtt.CONF_COMMAND_TOPIC),
@ -111,13 +143,15 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
Representation of an alarm status.
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to the previous state
or disarm if `disarm_after_trigger` is true.
When triggered, will be pending for the triggering state's 'delay_time'
plus the triggered state's 'pending_time'.
After that will be triggered for 'trigger_time', after that we return to
the previous state or disarm if `disarm_after_trigger` is true.
A trigger_time of zero disables the alarm_trigger service.
"""
def __init__(self, hass, name, code, pending_time,
trigger_time, disarm_after_trigger,
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):
@ -125,17 +159,24 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._pending_time = datetime.timedelta(seconds=pending_time)
self._trigger_time = datetime.timedelta(seconds=trigger_time)
if code_template:
self._code = code_template
self._code.hass = hass
else:
self._code = code or None
self._disarm_after_trigger = disarm_after_trigger
self._pre_trigger_state = self._state
self._previous_state = self._state
self._state_ts = None
self._pending_time_by_state = {}
for state in SUPPORTED_PENDING_STATES:
self._pending_time_by_state[state] = datetime.timedelta(
seconds=config[state][CONF_PENDING_TIME])
self._delay_time_by_state = {
state: config[state][CONF_DELAY_TIME]
for state in SUPPORTED_PRETRIGGER_STATES}
self._trigger_time_by_state = {
state: config[state][CONF_TRIGGER_TIME]
for state in SUPPORTED_PRETRIGGER_STATES}
self._pending_time_by_state = {
state: config[state][CONF_PENDING_TIME]
for state in SUPPORTED_PENDING_STATES}
self._state_topic = state_topic
self._command_topic = command_topic
@ -158,15 +199,16 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state == STATE_ALARM_TRIGGERED:
if self._within_pending_time(self._state):
return STATE_ALARM_PENDING
elif (self._state_ts + self._pending_time_by_state[self._state] +
self._trigger_time) < dt_util.utcnow():
trigger_time = self._trigger_time_by_state[self._previous_state]
if (self._state_ts + self._pending_time(self._state) +
trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
self._state = self._pre_trigger_state
self._state = self._previous_state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \
@ -175,9 +217,21 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return self._state
def _within_pending_time(self, state):
@property
def _active_state(self):
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
def _pending_time(self, state):
pending_time = self._pending_time_by_state[state]
return self._state_ts + pending_time > dt_util.utcnow()
if state == STATE_ALARM_TRIGGERED:
pending_time += self._delay_time_by_state[self._previous_state]
return pending_time
def _within_pending_time(self, state):
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
@property
def code_format(self):
@ -215,26 +269,35 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_ARMED_NIGHT)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state
"""
Send alarm trigger command.
No code needed, a trigger time of zero for the current state
disables the alarm.
"""
if not self._trigger_time_by_state[self._active_state]:
return
self._update_state(STATE_ALARM_TRIGGERED)
def _update_state(self, state):
if self._state == state:
return
self._previous_state = self._state
self._state = state
self._state_ts = dt_util.utcnow()
self.schedule_update_ha_state()
pending_time = self._pending_time_by_state[state]
if state == STATE_ALARM_TRIGGERED and self._trigger_time:
pending_time = self._pending_time(state)
if state == STATE_ALARM_TRIGGERED:
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + pending_time)
trigger_time = self._trigger_time_by_state[self._previous_state]
track_point_in_time(
self._hass, self.async_update_ha_state,
self._state_ts + self._trigger_time + pending_time)
self._state_ts + pending_time + trigger_time)
elif state in SUPPORTED_PENDING_STATES and pending_time:
track_point_in_time(
self._hass, self.async_update_ha_state,
@ -242,7 +305,14 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
def _validate_code(self, code, state):
"""Validate given code."""
check = self._code is None or code == self._code
if self._code is None:
return True
if isinstance(self._code, str):
alarm_code = self._code
else:
alarm_code = self._code.render(from_state=self._state,
to_state=state)
check = not alarm_code or code == alarm_code
if not check:
_LOGGER.warning("Invalid code given for %s", state)
return check
@ -253,6 +323,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
state_attr = {}
if self.state == STATE_ALARM_PENDING:
state_attr[ATTR_PRE_PENDING_STATE] = self._previous_state
state_attr[ATTR_POST_PENDING_STATE] = self._state
return state_attr

View file

@ -14,7 +14,9 @@ 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_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME,
STATE_ALARM_ARMED_CUSTOM_BYPASS)
REQUIREMENTS = ['total_connect_client==0.16']
@ -76,6 +78,8 @@ class TotalConnect(alarm.AlarmControlPanel):
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_STAY_NIGHT:
state = STATE_ALARM_ARMED_NIGHT
elif status == self._client.ARMED_CUSTOM_BYPASS:
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
elif status == self._client.ARMING:
state = STATE_ALARM_ARMING
elif status == self._client.DISARMING:

View file

@ -4,16 +4,13 @@ Support for AlarmDecoder devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alarmdecoder/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.discovery import load_platform
REQUIREMENTS = ['alarmdecoder==0.12.3']
@ -71,9 +68,9 @@ ZONE_SCHEMA = vol.Schema({
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICE): vol.Any(DEVICE_SOCKET_SCHEMA,
DEVICE_SERIAL_SCHEMA,
DEVICE_USB_SCHEMA),
vol.Required(CONF_DEVICE): vol.Any(
DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA,
DEVICE_USB_SCHEMA),
vol.Optional(CONF_PANEL_DISPLAY,
default=DEFAULT_PANEL_DISPLAY): cv.boolean,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
@ -81,8 +78,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass, config):
def setup(hass, config):
"""Set up for the AlarmDecoder devices."""
from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice)
@ -99,32 +95,25 @@ def async_setup(hass, config):
path = DEFAULT_DEVICE_PATH
baud = DEFAULT_DEVICE_BAUD
sync_connect = asyncio.Future(loop=hass.loop)
def handle_open(device):
"""Handle the successful connection."""
_LOGGER.info("Established a connection with the alarmdecoder")
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
sync_connect.set_result(True)
@callback
def stop_alarmdecoder(event):
"""Handle the shutdown of AlarmDecoder."""
_LOGGER.debug("Shutting down alarmdecoder")
controller.close()
@callback
def handle_message(sender, message):
"""Handle message from AlarmDecoder."""
async_dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message)
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_PANEL_MESSAGE, message)
def zone_fault_callback(sender, zone):
"""Handle zone fault from AlarmDecoder."""
async_dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone)
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_ZONE_FAULT, zone)
def zone_restore_callback(sender, zone):
"""Handle zone restore from AlarmDecoder."""
async_dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone)
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_ZONE_RESTORE, zone)
controller = False
if device_type == 'socket':
@ -139,7 +128,6 @@ def async_setup(hass, config):
AlarmDecoder(USBDevice.find())
return False
controller.on_open += handle_open
controller.on_message += handle_message
controller.on_zone_fault += zone_fault_callback
controller.on_zone_restore += zone_restore_callback
@ -148,21 +136,16 @@ def async_setup(hass, config):
controller.open(baud)
result = yield from sync_connect
_LOGGER.debug("Established a connection with the alarmdecoder")
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
if not result:
return False
hass.async_add_job(
async_load_platform(hass, 'alarm_control_panel', DOMAIN, conf,
config))
load_platform(hass, 'alarm_control_panel', DOMAIN, conf, config)
if zones:
hass.async_add_job(async_load_platform(
hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config))
load_platform(
hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config)
if display:
hass.async_add_job(async_load_platform(
hass, 'sensor', DOMAIN, conf, config))
load_platform(hass, 'sensor', DOMAIN, conf, config)
return True

View file

@ -18,7 +18,7 @@ from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.8']
REQUIREMENTS = ['pyatv==0.3.9']
_LOGGER = logging.getLogger(__name__)

View file

@ -34,6 +34,7 @@ DEVICE_CLASSES = [
'plug', # On means plugged in, Off means unplugged
'power', # Power, over-current, etc
'presence', # On means home, Off means away
'problem', # On means there is a problem, Off means the status is OK
'safety', # Generic on=unsafe, off=safe
'smoke', # Smoke detector
'sound', # On means sound detected, Off means no sound

View file

@ -0,0 +1,87 @@
"""
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
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Binary Sensor platform for ADS."""
ads_hub = hass.data.get(DATA_ADS)
ads_var = config.get(CONF_ADS_VAR)
name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS)
ads_sensor = AdsBinarySensor(ads_hub, name, ads_var, device_class)
add_devices([ads_sensor])
class AdsBinarySensor(BinarySensorDevice):
"""Representation of ADS binary sensors."""
def __init__(self, ads_hub, name, ads_var, device_class):
"""Initialize AdsBinarySensor entity."""
self._name = name
self._state = False
self._device_class = device_class or 'moving'
self._ads_hub = ads_hub
self.ads_var = ads_var
@asyncio.coroutine
def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notifications."""
_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
)
@property
def name(self):
"""Return the default name of the binary sensor."""
return self._name
@property
def device_class(self):
"""Return the device class."""
return self._device_class
@property
def is_on(self):
"""Return if the binary sensor is on."""
return self._state
@property
def should_poll(self):
"""Return False because entity pushes its state to HA."""
return False

View file

@ -7,39 +7,29 @@ https://home-assistant.io/components/binary_sensor.alarmdecoder/
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.alarmdecoder import (ZONE_SCHEMA,
CONF_ZONES,
CONF_ZONE_NAME,
CONF_ZONE_TYPE,
SIGNAL_ZONE_FAULT,
SIGNAL_ZONE_RESTORE)
from homeassistant.components.alarmdecoder import (
ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE,
SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE)
DEPENDENCIES = ['alarmdecoder']
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the AlarmDecoder binary sensor devices."""
configured_zones = discovery_info[CONF_ZONES]
devices = []
for zone_num in configured_zones:
device_config_data = ZONE_SCHEMA(configured_zones[zone_num])
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = AlarmDecoderBinarySensor(
hass, zone_num, zone_name, zone_type)
device = AlarmDecoderBinarySensor(zone_num, zone_name, zone_type)
devices.append(device)
async_add_devices(devices)
add_devices(devices)
return True
@ -47,7 +37,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Representation of an AlarmDecoder binary sensor."""
def __init__(self, hass, zone_number, zone_name, zone_type):
def __init__(self, zone_number, zone_name, zone_type):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
self._zone_type = zone_type
@ -55,16 +45,14 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._name = zone_name
self._type = zone_type
_LOGGER.debug("Setup up zone: %s", self._name)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONE_FAULT, self._fault_callback)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_ZONE_FAULT, self._fault_callback)
async_dispatcher_connect(
self.hass, SIGNAL_ZONE_RESTORE, self._restore_callback)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_ZONE_RESTORE, self._restore_callback)
@property
def name(self):
@ -97,16 +85,14 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type
@callback
def _fault_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self._state = 1
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
@callback
def _restore_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self._state = 0
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()

View file

@ -4,24 +4,31 @@ Support for ISY994 binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.isy994/
"""
import asyncio
import logging
from datetime import timedelta
from typing import Callable # noqa
from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
False: STATE_OFF,
True: STATE_ON,
}
UOM = ['2', '78']
STATES = [STATE_OFF, STATE_ON, 'true', 'false']
ISY_DEVICE_TYPES = {
'moisture': ['16.8', '16.13', '16.14'],
'opening': ['16.9', '16.6', '16.7', '16.2', '16.17', '16.20', '16.21'],
'motion': ['16.1', '16.4', '16.5', '16.3']
}
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
@ -32,10 +39,46 @@ def setup_platform(hass, config: ConfigType,
return False
devices = []
devices_by_nid = {}
child_nodes = []
for node in isy.filter_nodes(isy.SENSOR_NODES, units=UOM,
states=STATES):
devices.append(ISYBinarySensorDevice(node))
if node.parent_node is None:
device = ISYBinarySensorDevice(node)
devices.append(device)
devices_by_nid[node.nid] = device
else:
# We'll process the child nodes last, to ensure all parent nodes
# have been processed
child_nodes.append(node)
for node in child_nodes:
try:
parent_device = devices_by_nid[node.parent_node.nid]
except KeyError:
_LOGGER.error("Node %s has a parent node %s, but no device "
"was created for the parent. Skipping.",
node.nid, node.parent_nid)
else:
device_type = _detect_device_type(node)
if device_type in ['moisture', 'opening']:
subnode_id = int(node.nid[-1])
# Leak and door/window sensors work the same way with negative
# nodes and heartbeat nodes
if subnode_id == 4:
# Subnode 4 is the heartbeat node, which we will represent
# as a separate binary_sensor
device = ISYBinarySensorHeartbeat(node, parent_device)
parent_device.add_heartbeat_device(device)
devices.append(device)
elif subnode_id == 2:
parent_device.add_negative_node(node)
else:
# We don't yet have any special logic for other sensor types,
# so add the nodes as individual devices
device = ISYBinarySensorDevice(node)
devices.append(device)
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
@ -48,23 +91,281 @@ def setup_platform(hass, config: ConfigType,
add_devices(devices)
def _detect_device_type(node) -> str:
try:
device_type = node.type
except AttributeError:
# The type attribute didn't exist in the ISY's API response
return None
split_type = device_type.split('.')
for device_class, ids in ISY_DEVICE_TYPES.items():
if '{}.{}'.format(split_type[0], split_type[1]) in ids:
return device_class
return None
def _is_val_unknown(val):
"""Determine if a number value represents UNKNOWN from PyISY."""
return val == -1*float('inf')
class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor device."""
"""Representation of an ISY994 binary sensor device.
Often times, a single device is represented by multiple nodes in the ISY,
allowing for different nuances in how those devices report their on and
off events. This class turns those multiple nodes in to a single Hass
entity and handles both ways that ISY binary sensors can work.
"""
def __init__(self, node) -> None:
"""Initialize the ISY994 binary sensor device."""
isy.ISYDevice.__init__(self, node)
super().__init__(node)
self._negative_node = None
self._heartbeat_device = None
self._device_class_from_type = _detect_device_type(self._node)
# pylint: disable=protected-access
if _is_val_unknown(self._node.status._val):
self._computed_state = None
else:
self._computed_state = bool(self._node.status._val)
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters."""
yield from super().async_added_to_hass()
self._node.controlEvents.subscribe(self._positive_node_control_handler)
if self._negative_node is not None:
self._negative_node.controlEvents.subscribe(
self._negative_node_control_handler)
def add_heartbeat_device(self, device) -> None:
"""Register a heartbeat device for this sensor.
The heartbeat node beats on its own, but we can gain a little
reliability by considering any node activity for this sensor
to be a heartbeat as well.
"""
self._heartbeat_device = device
def _heartbeat(self) -> None:
"""Send a heartbeat to our heartbeat device, if we have one."""
if self._heartbeat_device is not None:
self._heartbeat_device.heartbeat()
def add_negative_node(self, child) -> None:
"""Add a negative node to this binary sensor device.
The negative node is a node that can receive the 'off' events
for the sensor, depending on device configuration and type.
"""
self._negative_node = child
if not _is_val_unknown(self._negative_node):
# If the negative node has a value, it means the negative node is
# in use for this device. Therefore, we cannot determine the state
# of the sensor until we receive our first ON event.
self._computed_state = None
def _negative_node_control_handler(self, event: object) -> None:
"""Handle an "On" control event from the "negative" node."""
if event == 'DON':
_LOGGER.debug("Sensor %s turning Off via the Negative node "
"sending a DON command", self.name)
self._computed_state = False
self.schedule_update_ha_state()
self._heartbeat()
def _positive_node_control_handler(self, event: object) -> None:
"""Handle On and Off control event coming from the primary node.
Depending on device configuration, sometimes only On events
will come to this node, with the negative node representing Off
events
"""
if event == 'DON':
_LOGGER.debug("Sensor %s turning On via the Primary node "
"sending a DON command", self.name)
self._computed_state = True
self.schedule_update_ha_state()
self._heartbeat()
if event == 'DOF':
_LOGGER.debug("Sensor %s turning Off via the Primary node "
"sending a DOF command", self.name)
self._computed_state = False
self.schedule_update_ha_state()
self._heartbeat()
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore primary node status updates.
We listen directly to the Control events on all nodes for this
device.
"""
pass
@property
def value(self) -> object:
"""Get the current value of the device.
Insteon leak sensors set their primary node to On when the state is
DRY, not WET, so we invert the binary state if the user indicates
that it is a moisture sensor.
"""
if self._computed_state is None:
# Do this first so we don't invert None on moisture sensors
return None
if self.device_class == 'moisture':
return not self._computed_state
return self._computed_state
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.
Note: This method will return false if the current state is UNKNOWN
"""
return bool(self.value)
@property
def state(self):
"""Return the state of the binary sensor."""
if self._computed_state is None:
return None
return STATE_ON if self.is_on else STATE_OFF
@property
def device_class(self) -> str:
"""Return the class of this device.
This was discovered by parsing the device type code during init
"""
return self._device_class_from_type
class ISYBinarySensorHeartbeat(isy.ISYDevice, BinarySensorDevice):
"""Representation of the battery state of an ISY994 sensor."""
def __init__(self, node, parent_device) -> None:
"""Initialize the ISY994 binary sensor device."""
super().__init__(node)
self._computed_state = None
self._parent_device = parent_device
self._heartbeat_timer = None
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters."""
yield from super().async_added_to_hass()
self._node.controlEvents.subscribe(
self._heartbeat_node_control_handler)
# Start the timer on bootup, so we can change from UNKNOWN to ON
self._restart_timer()
def _heartbeat_node_control_handler(self, event: object) -> None:
"""Update the heartbeat timestamp when an On event is sent."""
if event == 'DON':
self.heartbeat()
def heartbeat(self):
"""Mark the device as online, and restart the 25 hour timer.
This gets called when the heartbeat node beats, but also when the
parent sensor sends any events, as we can trust that to mean the device
is online. This mitigates the risk of false positives due to a single
missed heartbeat event.
"""
self._computed_state = False
self._restart_timer()
self.schedule_update_ha_state()
def _restart_timer(self):
"""Restart the 25 hour timer."""
try:
self._heartbeat_timer()
self._heartbeat_timer = None
except TypeError:
# No heartbeat timer is active
pass
# pylint: disable=unused-argument
@callback
def timer_elapsed(now) -> None:
"""Heartbeat missed; set state to indicate dead battery."""
self._computed_state = True
self._heartbeat_timer = None
self.schedule_update_ha_state()
point_in_time = dt_util.utcnow() + timedelta(hours=25)
_LOGGER.debug("Timer starting. Now: %s Then: %s",
dt_util.utcnow(), point_in_time)
self._heartbeat_timer = async_track_point_in_utc_time(
self.hass, timer_elapsed, point_in_time)
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore node status updates.
We listen directly to the Control events for this device.
"""
pass
@property
def value(self) -> object:
"""Get the current value of this sensor."""
return self._computed_state
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.
Note: This method will return false if the current state is UNKNOWN
"""
return bool(self.value)
@property
def state(self):
"""Return the state of the binary sensor."""
if self._computed_state is None:
return None
return STATE_ON if self.is_on else STATE_OFF
@property
def device_class(self) -> str:
"""Get the class of this device."""
return 'battery'
@property
def device_state_attributes(self):
"""Get the state attributes for the device."""
attr = super().device_state_attributes
attr['parent_entity_id'] = self._parent_device.entity_id
return attr
class ISYBinarySensorProgram(isy.ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor program.
This does not need all of the subnode logic in the device version of binary
sensors.
"""
def __init__(self, name, node) -> None:
"""Initialize the ISY994 binary sensor program."""
super().__init__(node)
self._name = name
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on."""
return bool(self.value)
class ISYBinarySensorProgram(ISYBinarySensorDevice):
"""Representation of an ISY994 binary sensor program."""
def __init__(self, name, node) -> None:
"""Initialize the ISY994 binary sensor program."""
ISYBinarySensorDevice.__init__(self, node)
self._name = name

View file

@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_devices(
VeraBinarySensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['binary_sensor'])
VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor'])
class VeraBinarySensor(VeraDevice, BinarySensorDevice):

View file

@ -0,0 +1,230 @@
"""
Support for WebDav Calendar.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar.caldav/
"""
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)
from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
from homeassistant.util import dt, Throttle
REQUIREMENTS = ['caldav==0.5.0']
_LOGGER = logging.getLogger(__name__)
CONF_DEVICE_ID = 'device_id'
CONF_CALENDARS = 'calendars'
CONF_CUSTOM_CALENDARS = 'custom_calendars'
CONF_CALENDAR = 'calendar'
CONF_ALL_DAY = 'all_day'
CONF_SEARCH = 'search'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_URL): vol.Url,
vol.Optional(CONF_CALENDARS, default=[]):
vol.All(cv.ensure_list, vol.Schema([
cv.string
])),
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
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
})
]))
})
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
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))
# Retrieve all the remote calendars
calendars = client.principal().calendars()
calendar_devices = []
for calendar in list(calendars):
# If a calendar name was given in the configuration,
# ignore all the others
if (config.get(CONF_CALENDARS)
and calendar.name not in config.get(CONF_CALENDARS)):
_LOGGER.debug("Ignoring calendar '%s'", calendar.name)
continue
# 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:
continue
device_data = {
CONF_NAME: cust_calendar.get(CONF_NAME),
CONF_DEVICE_ID: "{} {}".format(
cust_calendar.get(CONF_CALENDAR),
cust_calendar.get(CONF_NAME)),
}
calendar_devices.append(
WebDavCalendarEventDevice(hass,
device_data,
calendar,
cust_calendar.get(CONF_ALL_DAY),
cust_calendar.get(CONF_SEARCH))
)
# Create a default calendar if there was no custom one
if not config.get(CONF_CUSTOM_CALENDARS):
device_data = {
CONF_NAME: calendar.name,
CONF_DEVICE_ID: calendar.name
}
calendar_devices.append(
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,
search=None):
"""Create the WebDav Calendar Event Device."""
self.data = WebDavCalendarData(calendar, all_day, search)
super().__init__(hass, device_data)
@property
def device_state_attributes(self):
"""Return the device state attributes."""
if self.data.event is None:
# No tasks, we don't REALLY need to show anything.
return {}
attributes = super().device_state_attributes
return attributes
class WebDavCalendarData(object):
"""Class to utilize the calendar dav client object to get next event."""
def __init__(self, calendar, include_all_day, search):
"""Set up how we are going to search the WebDav calendar."""
self.calendar = calendar
self.include_all_day = include_all_day
self.search = search
self.event = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
# We have to retrieve the results for the whole day as the server
# won't return events that have already started
results = self.calendar.date_search(
dt.start_of_local_day(),
dt.start_of_local_day() + timedelta(days=1)
)
# dtstart can be a date or datetime depending if the event lasts a
# whole day. Convert everything to datetime to be able to sort it
results.sort(key=lambda x: self.to_datetime(
x.instance.vevent.dtstart.value
))
vevent = next((
event.instance.vevent for event in results
if (self.is_matching(event.instance.vevent, self.search)
and (not self.is_all_day(event.instance.vevent)
or self.include_all_day)
and not self.is_over(event.instance.vevent))), None)
# If no matching event could be found
if vevent is None:
_LOGGER.debug(
"No matching event found in the %d results for %s",
len(results),
self.calendar.name,
)
self.event = None
return True
# Populate the entity attributes with the event values
self.event = {
"summary": vevent.summary.value,
"start": self.get_hass_date(vevent.dtstart.value),
"end": self.get_hass_date(vevent.dtend.value),
"location": self.get_attr_value(vevent, "location"),
"description": self.get_attr_value(vevent, "description")
}
return True
@staticmethod
def is_matching(vevent, search):
"""Return if the event matches the filter critera."""
if search is None:
return True
pattern = re.compile(search)
return (hasattr(vevent, "summary")
and pattern.match(vevent.summary.value)
or hasattr(vevent, "location")
and pattern.match(vevent.location.value)
or hasattr(vevent, "description")
and pattern.match(vevent.description.value))
@staticmethod
def is_all_day(vevent):
"""Return if the event last the whole day."""
return not isinstance(vevent.dtstart.value, datetime)
@staticmethod
def is_over(vevent):
"""Return if the event is over."""
return dt.now() > WebDavCalendarData.to_datetime(vevent.dtend.value)
@staticmethod
def get_hass_date(obj):
"""Return if the event matches."""
if isinstance(obj, datetime):
return {"dateTime": obj.isoformat()}
return {"date": obj.isoformat()}
@staticmethod
def to_datetime(obj):
"""Return a datetime."""
if isinstance(obj, datetime):
return obj
return dt.as_local(dt.dt.datetime.combine(obj, dt.dt.time.min))
@staticmethod
def get_attr_value(obj, attribute):
"""Return the value of the attribute if defined."""
if hasattr(obj, attribute):
return getattr(obj, attribute).value
return None

View file

@ -0,0 +1,95 @@
"""
Support for Canary camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.canary/
"""
import logging
import requests
from homeassistant.components.camera import Camera
from homeassistant.components.canary import DATA_CANARY, DEFAULT_TIMEOUT
DEPENDENCIES = ['canary']
_LOGGER = logging.getLogger(__name__)
ATTR_MOTION_START_TIME = "motion_start_time"
ATTR_MOTION_END_TIME = "motion_end_time"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Canary sensors."""
data = hass.data[DATA_CANARY]
devices = []
for location in data.locations:
entries = data.get_motion_entries(location.location_id)
if entries:
devices.append(CanaryCamera(data, location.location_id,
DEFAULT_TIMEOUT))
add_devices(devices, True)
class CanaryCamera(Camera):
"""An implementation of a Canary security camera."""
def __init__(self, data, location_id, timeout):
"""Initialize a Canary security camera."""
super().__init__()
self._data = data
self._location_id = location_id
self._timeout = timeout
self._location = None
self._motion_entry = None
self._image_content = None
def camera_image(self):
"""Update the status of the camera and return bytes of camera image."""
self.update()
return self._image_content
@property
def name(self):
"""Return the name of this device."""
return self._location.name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._location.is_recording
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
if self._motion_entry is None:
return None
return {
ATTR_MOTION_START_TIME: self._motion_entry.start_time,
ATTR_MOTION_END_TIME: self._motion_entry.end_time,
}
def update(self):
"""Update the status of the camera."""
self._data.update()
self._location = self._data.get_location(self._location_id)
entries = self._data.get_motion_entries(self._location_id)
if entries:
current = entries[0]
previous = self._motion_entry
if previous is None or previous.entry_id != current.entry_id:
self._motion_entry = current
self._image_content = requests.get(
current.thumbnails[0].image_url,
timeout=self._timeout).content
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return not self._location.is_recording

View file

@ -0,0 +1,117 @@
"""
Support for Canary.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/canary/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from requests import ConnectTimeout, HTTPError
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
REQUIREMENTS = ['py-canary==0.2.3']
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_ID = 'canary_notification'
NOTIFICATION_TITLE = 'Canary Setup'
DOMAIN = 'canary'
DATA_CANARY = 'canary'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
DEFAULT_TIMEOUT = 10
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}),
}, extra=vol.ALLOW_EXTRA)
CANARY_COMPONENTS = [
'alarm_control_panel', 'camera', 'sensor'
]
def setup(hass, config):
"""Set up the Canary component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
timeout = conf.get(CONF_TIMEOUT)
try:
hass.data[DATA_CANARY] = CanaryData(username, password, timeout)
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Canary service: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
for component in CANARY_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
class CanaryData(object):
"""Get the latest data and update the states."""
def __init__(self, username, password, timeout):
"""Init the Canary data object."""
from canary.api import Api
self._api = Api(username, password, timeout)
self._locations_by_id = {}
self._readings_by_device_id = {}
self._entries_by_location_id = {}
self.update()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs):
"""Get the latest data from py-canary."""
for location in self._api.get_locations():
location_id = location.location_id
self._locations_by_id[location_id] = location
self._entries_by_location_id[location_id] = self._api.get_entries(
location_id, entry_type="motion", limit=1)
for device in location.devices:
if device.is_online:
self._readings_by_device_id[device.device_id] = \
self._api.get_latest_readings(device.device_id)
@property
def locations(self):
"""Return a list of locations."""
return self._locations_by_id.values()
def get_motion_entries(self, location_id):
"""Return a list of motion entries based on location_id."""
return self._entries_by_location_id.get(location_id, [])
def get_location(self, location_id):
"""Return a location based on location_id."""
return self._locations_by_id.get(location_id, [])
def get_readings(self, device_id):
"""Return a list of readings based on device_id."""
return self._readings_by_device_id.get(device_id, [])
def set_location_mode(self, location_id, mode_name, is_private=False):
"""Set location mode."""
self._api.set_location_mode(location_id, mode_name, is_private)
self.update(no_throttle=True)

View file

@ -13,7 +13,8 @@ from homeassistant.core import callback
from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
STATE_AUTO, ATTR_OPERATION_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
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)
@ -40,7 +41,7 @@ CONF_MIN_DUR = 'min_cycle_duration'
CONF_COLD_TOLERANCE = 'cold_tolerance'
CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -58,6 +59,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF])
})
@ -75,11 +78,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
initial_operation_mode = config.get(CONF_INITIAL_OPERATION_MODE)
async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
hot_tolerance, keep_alive)])
hot_tolerance, keep_alive, initial_operation_mode)])
class GenericThermostat(ClimateDevice):
@ -87,7 +91,8 @@ class GenericThermostat(ClimateDevice):
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
cold_tolerance, hot_tolerance, keep_alive):
cold_tolerance, hot_tolerance, keep_alive,
initial_operation_mode):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
@ -97,7 +102,11 @@ class GenericThermostat(ClimateDevice):
self._cold_tolerance = cold_tolerance
self._hot_tolerance = hot_tolerance
self._keep_alive = keep_alive
self._enabled = True
self._initial_operation_mode = initial_operation_mode
if initial_operation_mode == STATE_OFF:
self._enabled = False
else:
self._enabled = True
self._active = False
self._cur_temp = None
@ -122,14 +131,20 @@ class GenericThermostat(ClimateDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Run when entity about to be added."""
# If we have an old state and no target temp, restore
if self._target_temp is None:
old_state = yield from async_get_last_state(self.hass,
self.entity_id)
if old_state is not None:
# Check If we have an old state
old_state = yield from async_get_last_state(self.hass,
self.entity_id)
if old_state is not None:
# If we have no initial temperature, restore
if self._target_temp is None:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
# If we have no initial operation mode, restore
if self._initial_operation_mode is None:
if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF:
self._enabled = False
@property
def should_poll(self):
"""Return the polling state."""

View file

@ -59,8 +59,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
climate_devices = []
for zone in zones:
climate_devices.append(create_climate_device(
tado, hass, zone, zone['name'], zone['id']))
device = create_climate_device(
tado, hass, zone, zone['name'], zone['id'])
if not device:
continue
climate_devices.append(device)
if climate_devices:
add_devices(climate_devices, True)
@ -75,8 +78,11 @@ def create_climate_device(tado, hass, zone, name, zone_id):
if ac_mode:
temperatures = capabilities['HEAT']['temperatures']
else:
elif 'temperatures' in capabilities:
temperatures = capabilities['temperatures']
else:
_LOGGER.debug("Received zone %s has no temperature; not adding", name)
return
min_temp = float(temperatures['celsius']['min'])
max_temp = float(temperatures['celsius']['max'])

View file

@ -32,8 +32,8 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up of Vera thermostats."""
add_devices_callback(
VeraThermostat(device, VERA_CONTROLLER) for
device in VERA_DEVICES['climate'])
VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['climate'])
class VeraThermostat(VeraDevice, ClimateDevice):

View file

@ -16,7 +16,7 @@ from homeassistant.components.alexa import smart_home
from . import http_api, iot
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.5.0']
REQUIREMENTS = ['warrant==0.6.1']
_LOGGER = logging.getLogger(__name__)
@ -27,7 +27,7 @@ CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
MODE_DEV = 'development'
DEFAULT_MODE = MODE_DEV
DEFAULT_MODE = 'production'
DEPENDENCIES = ['http']
ALEXA_SCHEMA = vol.Schema({
@ -42,10 +42,10 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_MODE, default=DEFAULT_MODE):
vol.In([MODE_DEV] + list(SERVERS)),
# Change to optional when we include real servers
vol.Required(CONF_COGNITO_CLIENT_ID): str,
vol.Required(CONF_USER_POOL_ID): str,
vol.Required(CONF_REGION): str,
vol.Required(CONF_RELAYER): str,
vol.Optional(CONF_COGNITO_CLIENT_ID): str,
vol.Optional(CONF_USER_POOL_ID): str,
vol.Optional(CONF_REGION): str,
vol.Optional(CONF_RELAYER): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA
}),
}, extra=vol.ALLOW_EXTRA)
@ -117,10 +117,6 @@ class Cloud:
@property
def subscription_expired(self):
"""Return a boolen if the subscription has expired."""
# For now, don't enforce subscriptions to exist
if 'custom:sub-exp' not in self.claims:
return False
return dt_util.utcnow() > self.expiration_date
@property

View file

@ -68,11 +68,14 @@ def register(cloud, email, password):
from botocore.exceptions import ClientError
cognito = _cognito(cloud)
# Workaround for bug in Warrant. PR with fix:
# https://github.com/capless/warrant/pull/82
cognito.add_base_attributes()
try:
if cloud.cognito_email_based:
cognito.register(email, password, email=email)
cognito.register(email, password)
else:
cognito.register(_generate_username(email), password, email=email)
cognito.register(_generate_username(email), password)
except ClientError as err:
raise _map_aws_exception(err)

View file

@ -4,13 +4,12 @@ CONFIG_DIR = '.cloud'
REQUEST_TIMEOUT = 10
SERVERS = {
# Example entry:
# 'production': {
# 'cognito_client_id': '',
# 'user_pool_id': '',
# 'region': '',
# 'relayer': ''
# }
'production': {
'cognito_client_id': '60i2uvhvbiref2mftj7rgcrt9u',
'user_pool_id': 'us-east-1_87ll5WOP8',
'region': 'us-east-1',
'relayer': 'wss://cloud.hass.io:8000/websocket'
}
}
MESSAGE_EXPIRATION = """

View file

@ -1,4 +1,4 @@
"""Provide configuration end points for Z-Wave."""
"""Provide configuration end points for Automations."""
import asyncio
from homeassistant.components.config import EditIdBasedConfigView

View file

@ -69,7 +69,10 @@ class ISYCoverDevice(isy.ISYDevice, CoverDevice):
@property
def state(self) -> str:
"""Get the state of the ISY994 cover device."""
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
if self.is_unknown():
return None
else:
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""

View file

@ -0,0 +1,65 @@
"""
Support for Tellstick covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tellstick/
"""
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tellstick import (
DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG,
DATA_TELLSTICK, TellstickDevice)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tellstick covers."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return
signal_repetitions = discovery_info.get(
ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS)
add_devices([TellstickCover(hass.data[DATA_TELLSTICK][tellcore_id],
signal_repetitions)
for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]],
True)
class TellstickCover(TellstickDevice, CoverDevice):
"""Representation of a Tellstick cover."""
@property
def is_closed(self):
"""Return the current position of the cover is not possible."""
return None
@property
def assumed_state(self):
"""Return True if unable to access real state of the entity."""
return True
def close_cover(self, **kwargs):
"""Close the cover."""
self._tellcore_device.down()
def open_cover(self, **kwargs):
"""Open the cover."""
self._tellcore_device.up()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self._tellcore_device.stop()
def _parse_tellcore_data(self, tellcore_data):
"""Turn the value received from tellcore into something useful."""
pass
def _parse_ha_data(self, kwargs):
"""Turn the value from HA into something useful."""
pass
def _update_model(self, new_state, data):
"""Update the device entity state to match the arguments."""
pass

View file

@ -18,8 +18,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera covers."""
add_devices(
VeraCover(device, VERA_CONTROLLER) for
device in VERA_DEVICES['cover'])
VeraCover(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['cover'])
class VeraCover(VeraDevice, CoverDevice):

View file

@ -53,6 +53,7 @@ YAML_DEVICES = 'known_devices.yaml'
CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True
CONF_NEW_DEVICE_DEFAULTS = 'new_device_defaults'
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
@ -81,12 +82,18 @@ ATTR_VENDOR = 'vendor'
SOURCE_TYPE_GPS = 'gps'
SOURCE_TYPE_ROUTER = 'router'
NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(None, vol.Schema({
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean,
}))
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): cv.time_period,
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME,
default=DEFAULT_CONSIDER_HOME): vol.All(
cv.time_period, cv.positive_timedelta)
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_NEW_DEVICE_DEFAULTS,
default={}): NEW_DEVICE_DEFAULTS_SCHEMA
})
@ -125,9 +132,11 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
conf = conf[0] if conf else {}
consider_home = conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
defaults = conf.get(CONF_NEW_DEVICE_DEFAULTS, {})
devices = yield from async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(hass, consider_home, track_new, devices)
tracker = DeviceTracker(
hass, consider_home, track_new, defaults, devices)
@asyncio.coroutine
def async_setup_platform(p_type, p_config, disc_info=None):
@ -211,13 +220,15 @@ class DeviceTracker(object):
"""Representation of a device tracker."""
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track_new: bool, devices: Sequence) -> None:
track_new: bool, defaults: dict,
devices: Sequence) -> None:
"""Initialize a device tracker."""
self.hass = hass
self.devices = {dev.dev_id: dev for dev in devices}
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
self.consider_home = consider_home
self.track_new = track_new
self.track_new = defaults.get(CONF_TRACK_NEW, track_new)
self.defaults = defaults
self.group = None
self._is_updating = asyncio.Lock(loop=hass.loop)
@ -274,7 +285,8 @@ class DeviceTracker(object):
device = Device(
self.hass, self.consider_home, self.track_new,
dev_id, mac, (host_name or dev_id).replace('_', ' '),
picture=picture, icon=icon)
picture=picture, icon=icon,
hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
self.devices[dev_id] = device
if mac is not None:
self.mac_to_dev[mac] = device

View file

@ -0,0 +1,136 @@
"""
Support for the Meraki CMX location service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.meraki/
"""
import asyncio
import logging
import json
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (HTTP_BAD_REQUEST, HTTP_UNPROCESSABLE_ENTITY)
from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, SOURCE_TYPE_ROUTER)
CONF_VALIDATOR = 'validator'
CONF_SECRET = 'secret'
DEPENDENCIES = ['http']
URL = '/api/meraki'
VERSION = '2.0'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_VALIDATOR): cv.string,
vol.Required(CONF_SECRET): cv.string
})
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an endpoint for the Meraki tracker."""
hass.http.register_view(
MerakiView(config, async_see))
return True
class MerakiView(HomeAssistantView):
"""View to handle Meraki requests."""
url = URL
name = 'api:meraki'
def __init__(self, config, async_see):
"""Initialize Meraki URL endpoints."""
self.async_see = async_see
self.validator = config[CONF_VALIDATOR]
self.secret = config[CONF_SECRET]
@asyncio.coroutine
def get(self, request):
"""Meraki message received as GET."""
return self.validator
@asyncio.coroutine
def post(self, request):
"""Meraki CMX message received."""
try:
data = yield from request.json()
except ValueError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
_LOGGER.debug("Meraki Data from Post: %s", json.dumps(data))
if not data.get('secret', False):
_LOGGER.error("secret invalid")
return self.json_message('No secret', HTTP_UNPROCESSABLE_ENTITY)
if data['secret'] != self.secret:
_LOGGER.error("Invalid Secret received from Meraki")
return self.json_message('Invalid secret',
HTTP_UNPROCESSABLE_ENTITY)
elif data['version'] != VERSION:
_LOGGER.error("Invalid API version: %s", data['version'])
return self.json_message('Invalid version',
HTTP_UNPROCESSABLE_ENTITY)
else:
_LOGGER.debug('Valid Secret')
if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'):
_LOGGER.error("Unknown Device %s", data['type'])
return self.json_message('Invalid device type',
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.debug("Processing %s", data['type'])
if len(data["data"]["observations"]) == 0:
_LOGGER.debug("No observations found")
return
self._handle(request.app['hass'], data)
@callback
def _handle(self, hass, data):
for i in data["data"]["observations"]:
data["data"]["secret"] = "hidden"
lat = i["location"]["lat"]
lng = i["location"]["lng"]
try:
accuracy = int(float(i["location"]["unc"]))
except ValueError:
accuracy = 0
mac = i["clientMac"]
_LOGGER.debug("clientMac: %s", mac)
if lat == "NaN" or lng == "NaN":
_LOGGER.debug(
"No coordinates received, skipping location for: " + mac
)
gps_location = None
accuracy = None
else:
gps_location = (lat, lng)
attrs = {}
if i.get('os', False):
attrs['os'] = i['os']
if i.get('manufacturer', False):
attrs['manufacturer'] = i['manufacturer']
if i.get('ipv4', False):
attrs['ipv4'] = i['ipv4']
if i.get('ipv6', False):
attrs['ipv6'] = i['ipv6']
if i.get('seenTime', False):
attrs['seenTime'] = i['seenTime']
if i.get('ssid', False):
attrs['ssid'] = i['ssid']
hass.async_add_job(self.async_see(
gps=gps_location,
mac=mac,
source_type=SOURCE_TYPE_ROUTER,
gps_accuracy=accuracy,
attributes=attrs
))

View file

@ -36,6 +36,7 @@ SERVICE_APPLE_TV = 'apple_tv'
SERVICE_WINK = 'wink'
SERVICE_XIAOMI_GW = 'xiaomi_gw'
SERVICE_TELLDUSLIVE = 'tellstick'
SERVICE_HUE = 'philips_hue'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@ -48,7 +49,7 @@ SERVICE_HANDLERS = {
SERVICE_WINK: ('wink', None),
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_TELLDUSLIVE: ('tellduslive', None),
'philips_hue': ('light', 'hue'),
SERVICE_HUE: ('hue', None),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),

View file

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

View file

@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20171204.0', 'user-agents==1.1.0']
REQUIREMENTS = ['home-assistant-frontend==20171216.0', 'user-agents==1.1.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@ -35,7 +35,7 @@ CONF_EXTRA_HTML_URL = 'extra_html_url'
CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5'
CONF_FRONTEND_REPO = 'development_repo'
CONF_JS_VERSION = 'javascript_version'
JS_DEFAULT_OPTION = 'es5'
JS_DEFAULT_OPTION = 'auto'
JS_OPTIONS = ['es5', 'latest', 'auto']
DEFAULT_THEME_COLOR = '#03A9F4'
@ -299,11 +299,16 @@ def async_setup(hass, config):
hass.data[DATA_JS_VERSION] = js_version = conf.get(CONF_JS_VERSION)
if is_dev:
hass.http.register_static_path(
"/home-assistant-polymer", repo_path, False)
for subpath in ["src", "build-translations", "build-temp", "build",
"hass_frontend", "bower_components", "panels"]:
hass.http.register_static_path(
"/home-assistant-polymer/{}".format(subpath),
os.path.join(repo_path, subpath),
False)
hass.http.register_static_path(
"/static/translations",
os.path.join(repo_path, "build-translations"), False)
os.path.join(repo_path, "build-translations/output"), False)
sw_path_es5 = os.path.join(repo_path, "build-es5/service_worker.js")
sw_path_latest = os.path.join(repo_path, "build/service_worker.js")
static_path = os.path.join(repo_path, 'hass_frontend')

View file

@ -5,25 +5,26 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
"""
import asyncio
import os
import logging
from datetime import timedelta
from functools import partial
import logging
import os
import socket
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_USERNAME, CONF_PASSWORD,
CONF_PLATFORM, CONF_HOSTS, CONF_NAME, ATTR_ENTITY_ID)
EVENT_HOMEASSISTANT_STOP, CONF_USERNAME, CONF_PASSWORD, CONF_PLATFORM,
CONF_HOSTS, CONF_HOST, ATTR_ENTITY_ID, STATE_UNKNOWN)
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['pyhomematic==0.1.35']
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
REQUIREMENTS = ['pyhomematic==0.1.36']
DOMAIN = 'homematic'
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL_HUB = timedelta(seconds=300)
SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
@ -41,9 +42,11 @@ ATTR_CHANNEL = 'channel'
ATTR_NAME = 'name'
ATTR_ADDRESS = 'address'
ATTR_VALUE = 'value'
ATTR_PROXY = 'proxy'
ATTR_INTERFACE = 'interface'
ATTR_ERRORCODE = 'error'
ATTR_MESSAGE = 'message'
ATTR_MODE = 'mode'
ATTR_TIME = 'time'
EVENT_KEYPRESS = 'homematic.keypress'
EVENT_IMPULSE = 'homematic.impulse'
@ -51,8 +54,9 @@ EVENT_ERROR = 'homematic.error'
SERVICE_VIRTUALKEY = 'virtualkey'
SERVICE_RECONNECT = 'reconnect'
SERVICE_SET_VAR_VALUE = 'set_var_value'
SERVICE_SET_DEV_VALUE = 'set_dev_value'
SERVICE_SET_VARIABLE_VALUE = 'set_variable_value'
SERVICE_SET_DEVICE_VALUE = 'set_device_value'
SERVICE_SET_INSTALL_MODE = 'set_install_mode'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
@ -90,12 +94,14 @@ HM_ATTRIBUTE_SUPPORT = {
'RSSI_DEVICE': ['rssi', {}],
'VALVE_STATE': ['valve', {}],
'BATTERY_STATE': ['battery', {}],
'CONTROL_MODE': ['mode', {0: 'Auto',
1: 'Manual',
2: 'Away',
3: 'Boost',
4: 'Comfort',
5: 'Lowering'}],
'CONTROL_MODE': ['mode', {
0: 'Auto',
1: 'Manual',
2: 'Away',
3: 'Boost',
4: 'Comfort',
5: 'Lowering'
}],
'POWER': ['power', {}],
'CURRENT': ['current', {}],
'VOLTAGE': ['voltage', {}],
@ -114,8 +120,6 @@ HM_IMPULSE_EVENTS = [
'SEQUENCE_OK',
]
_LOGGER = logging.getLogger(__name__)
CONF_RESOLVENAMES_OPTIONS = [
'metadata',
'json',
@ -124,12 +128,12 @@ CONF_RESOLVENAMES_OPTIONS = [
]
DATA_HOMEMATIC = 'homematic'
DATA_DEVINIT = 'homematic_devinit'
DATA_STORE = 'homematic_store'
DATA_CONF = 'homematic_conf'
CONF_INTERFACES = 'interfaces'
CONF_LOCAL_IP = 'local_ip'
CONF_LOCAL_PORT = 'local_port'
CONF_IP = 'ip'
CONF_PORT = 'port'
CONF_PATH = 'path'
CONF_CALLBACK_IP = 'callback_ip'
@ -146,37 +150,33 @@ DEFAULT_PORT = 2001
DEFAULT_PATH = ''
DEFAULT_USERNAME = 'Admin'
DEFAULT_PASSWORD = ''
DEFAULT_VARIABLES = False
DEFAULT_DEVICES = True
DEFAULT_PRIMARY = False
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'homematic',
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_ADDRESS): cv.string,
vol.Required(ATTR_PROXY): cv.string,
vol.Required(ATTR_INTERFACE): cv.string,
vol.Optional(ATTR_CHANNEL, default=1): vol.Coerce(int),
vol.Optional(ATTR_PARAM): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOSTS): {cv.match_all: {
vol.Required(CONF_IP): cv.string,
vol.Optional(CONF_INTERFACES, default={}): {cv.match_all: {
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_VARIABLES, default=DEFAULT_VARIABLES):
cv.boolean,
vol.Optional(CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES):
vol.In(CONF_RESOLVENAMES_OPTIONS),
vol.Optional(CONF_DEVICES, default=DEFAULT_DEVICES): cv.boolean,
vol.Optional(CONF_PRIMARY, default=DEFAULT_PRIMARY): cv.boolean,
vol.Optional(CONF_CALLBACK_IP): cv.string,
vol.Optional(CONF_CALLBACK_PORT): cv.port,
}},
vol.Optional(CONF_HOSTS, default={}): {cv.match_all: {
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
}},
vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string,
vol.Optional(CONF_LOCAL_PORT, default=DEFAULT_LOCAL_PORT): cv.port,
}),
@ -186,61 +186,88 @@ SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({
vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
vol.Required(ATTR_CHANNEL): vol.Coerce(int),
vol.Required(ATTR_PARAM): cv.string,
vol.Optional(ATTR_PROXY): cv.string,
vol.Optional(ATTR_INTERFACE): cv.string,
})
SCHEMA_SERVICE_SET_VAR_VALUE = vol.Schema({
SCHEMA_SERVICE_SET_VARIABLE_VALUE = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SCHEMA_SERVICE_SET_DEV_VALUE = vol.Schema({
SCHEMA_SERVICE_SET_DEVICE_VALUE = vol.Schema({
vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
vol.Required(ATTR_CHANNEL): vol.Coerce(int),
vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper),
vol.Required(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_PROXY): cv.string,
vol.Optional(ATTR_INTERFACE): cv.string,
})
SCHEMA_SERVICE_RECONNECT = vol.Schema({})
SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({
vol.Required(ATTR_INTERFACE): cv.string,
vol.Optional(ATTR_TIME, default=60): cv.positive_int,
vol.Optional(ATTR_MODE, default=1):
vol.All(vol.Coerce(int), vol.In([1, 2])),
vol.Optional(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
})
def virtualkey(hass, address, channel, param, proxy=None):
@bind_hass
def virtualkey(hass, address, channel, param, interface=None):
"""Send virtual keypress to homematic controlller."""
data = {
ATTR_ADDRESS: address,
ATTR_CHANNEL: channel,
ATTR_PARAM: param,
ATTR_PROXY: proxy,
ATTR_INTERFACE: interface,
}
hass.services.call(DOMAIN, SERVICE_VIRTUALKEY, data)
def set_var_value(hass, entity_id, value):
@bind_hass
def set_variable_value(hass, entity_id, value):
"""Change value of a Homematic system variable."""
data = {
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: value,
}
hass.services.call(DOMAIN, SERVICE_SET_VAR_VALUE, data)
hass.services.call(DOMAIN, SERVICE_SET_VARIABLE_VALUE, data)
def set_dev_value(hass, address, channel, param, value, proxy=None):
"""Call setValue XML-RPC method of supplied proxy."""
@bind_hass
def set_device_value(hass, address, channel, param, value, interface=None):
"""Call setValue XML-RPC method of supplied interface."""
data = {
ATTR_ADDRESS: address,
ATTR_CHANNEL: channel,
ATTR_PARAM: param,
ATTR_VALUE: value,
ATTR_PROXY: proxy,
ATTR_INTERFACE: interface,
}
hass.services.call(DOMAIN, SERVICE_SET_DEV_VALUE, data)
hass.services.call(DOMAIN, SERVICE_SET_DEVICE_VALUE, data)
@bind_hass
def set_install_mode(hass, interface, mode=None, time=None, address=None):
"""Call setInstallMode XML-RPC method of supplied inteface."""
data = {
key: value for key, value in (
(ATTR_INTERFACE, interface),
(ATTR_MODE, mode),
(ATTR_TIME, time),
(ATTR_ADDRESS, address)
) if value
}
hass.services.call(DOMAIN, SERVICE_SET_INSTALL_MODE, data)
@bind_hass
def reconnect(hass):
"""Reconnect to CCU/Homegear."""
hass.services.call(DOMAIN, SERVICE_RECONNECT, {})
@ -250,31 +277,32 @@ def setup(hass, config):
"""Set up the Homematic component."""
from pyhomematic import HMConnection
hass.data[DATA_DEVINIT] = {}
conf = config[DOMAIN]
hass.data[DATA_CONF] = remotes = {}
hass.data[DATA_STORE] = set()
# Create hosts-dictionary for pyhomematic
remotes = {}
hosts = {}
for rname, rconfig in config[DOMAIN][CONF_HOSTS].items():
server = rconfig.get(CONF_IP)
for rname, rconfig in conf[CONF_INTERFACES].items():
remotes[rname] = {
'ip': socket.gethostbyname(rconfig.get(CONF_HOST)),
'port': rconfig.get(CONF_PORT),
'path': rconfig.get(CONF_PATH),
'resolvenames': rconfig.get(CONF_RESOLVENAMES),
'username': rconfig.get(CONF_USERNAME),
'password': rconfig.get(CONF_PASSWORD),
'callbackip': rconfig.get(CONF_CALLBACK_IP),
'callbackport': rconfig.get(CONF_CALLBACK_PORT),
'connect': True,
}
remotes[rname] = {}
remotes[rname][CONF_IP] = server
remotes[rname][CONF_PORT] = rconfig.get(CONF_PORT)
remotes[rname][CONF_PATH] = rconfig.get(CONF_PATH)
remotes[rname][CONF_RESOLVENAMES] = rconfig.get(CONF_RESOLVENAMES)
remotes[rname][CONF_USERNAME] = rconfig.get(CONF_USERNAME)
remotes[rname][CONF_PASSWORD] = rconfig.get(CONF_PASSWORD)
remotes[rname]['callbackip'] = rconfig.get(CONF_CALLBACK_IP)
remotes[rname]['callbackport'] = rconfig.get(CONF_CALLBACK_PORT)
if server not in hosts or rconfig.get(CONF_PRIMARY):
hosts[server] = {
CONF_VARIABLES: rconfig.get(CONF_VARIABLES),
CONF_NAME: rname,
}
hass.data[DATA_DEVINIT][rname] = rconfig.get(CONF_DEVICES)
for sname, sconfig in conf[CONF_HOSTS].items():
remotes[sname] = {
'ip': socket.gethostbyname(sconfig.get(CONF_HOST)),
'port': DEFAULT_PORT,
'username': sconfig.get(CONF_USERNAME),
'password': sconfig.get(CONF_PASSWORD),
'connect': False,
}
# Create server thread
bound_system_callback = partial(_system_callback_handler, hass, config)
@ -295,9 +323,8 @@ def setup(hass, config):
# Init homematic hubs
entity_hubs = []
for _, hub_data in hosts.items():
entity_hubs.append(HMHub(
hass, homematic, hub_data[CONF_NAME], hub_data[CONF_VARIABLES]))
for hub_name in conf[CONF_HOSTS].keys():
entity_hubs.append(HMHub(hass, homematic, hub_name))
# Register HomeMatic services
descriptions = load_yaml_config_file(
@ -331,8 +358,7 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_VIRTUALKEY, _hm_service_virtualkey,
descriptions[DOMAIN][SERVICE_VIRTUALKEY],
schema=SCHEMA_SERVICE_VIRTUALKEY)
descriptions[SERVICE_VIRTUALKEY], schema=SCHEMA_SERVICE_VIRTUALKEY)
def _service_handle_value(service):
"""Service to call setValue method for HomeMatic system variable."""
@ -354,9 +380,9 @@ def setup(hass, config):
hub.hm_set_variable(name, value)
hass.services.register(
DOMAIN, SERVICE_SET_VAR_VALUE, _service_handle_value,
descriptions[DOMAIN][SERVICE_SET_VAR_VALUE],
schema=SCHEMA_SERVICE_SET_VAR_VALUE)
DOMAIN, SERVICE_SET_VARIABLE_VALUE, _service_handle_value,
descriptions[SERVICE_SET_VARIABLE_VALUE],
schema=SCHEMA_SERVICE_SET_VARIABLE_VALUE)
def _service_handle_reconnect(service):
"""Service to reconnect all HomeMatic hubs."""
@ -364,8 +390,7 @@ def setup(hass, config):
hass.services.register(
DOMAIN, SERVICE_RECONNECT, _service_handle_reconnect,
descriptions[DOMAIN][SERVICE_RECONNECT],
schema=SCHEMA_SERVICE_RECONNECT)
descriptions[SERVICE_RECONNECT], schema=SCHEMA_SERVICE_RECONNECT)
def _service_handle_device(service):
"""Service to call setValue method for HomeMatic devices."""
@ -383,9 +408,23 @@ def setup(hass, config):
hmdevice.setValue(param, value, channel)
hass.services.register(
DOMAIN, SERVICE_SET_DEV_VALUE, _service_handle_device,
descriptions[DOMAIN][SERVICE_SET_DEV_VALUE],
schema=SCHEMA_SERVICE_SET_DEV_VALUE)
DOMAIN, SERVICE_SET_DEVICE_VALUE, _service_handle_device,
descriptions[SERVICE_SET_DEVICE_VALUE],
schema=SCHEMA_SERVICE_SET_DEVICE_VALUE)
def _service_handle_install_mode(service):
"""Service to set interface into install mode."""
interface = service.data.get(ATTR_INTERFACE)
mode = service.data.get(ATTR_MODE)
time = service.data.get(ATTR_TIME)
address = service.data.get(ATTR_ADDRESS)
homematic.setInstallMode(interface, t=time, mode=mode, address=address)
hass.services.register(
DOMAIN, SERVICE_SET_INSTALL_MODE, _service_handle_install_mode,
descriptions[SERVICE_SET_INSTALL_MODE],
schema=SCHEMA_SERVICE_SET_INSTALL_MODE)
return True
@ -395,10 +434,10 @@ def _system_callback_handler(hass, config, src, *args):
# New devices available at hub
if src == 'newDevices':
(interface_id, dev_descriptions) = args
proxy = interface_id.split('-')[-1]
interface = interface_id.split('-')[-1]
# Device support active?
if not hass.data[DATA_DEVINIT][proxy]:
if not hass.data[DATA_CONF][interface]['connect']:
return
addresses = []
@ -410,9 +449,9 @@ def _system_callback_handler(hass, config, src, *args):
# Register EVENTS
# Search all devices with an EVENTNODE that includes data
bound_event_callback = partial(_hm_event_handler, hass, proxy)
bound_event_callback = partial(_hm_event_handler, hass, interface)
for dev in addresses:
hmdevice = hass.data[DATA_HOMEMATIC].devices[proxy].get(dev)
hmdevice = hass.data[DATA_HOMEMATIC].devices[interface].get(dev)
if hmdevice.EVENTNODE:
hmdevice.setEventCallback(
@ -429,7 +468,7 @@ def _system_callback_handler(hass, config, src, *args):
('climate', DISCOVER_CLIMATE)):
# Get all devices of a specific type
found_devices = _get_devices(
hass, discovery_type, addresses, proxy)
hass, discovery_type, addresses, interface)
# When devices of this type are found
# they are setup in HASS and an discovery event is fired
@ -448,12 +487,12 @@ def _system_callback_handler(hass, config, src, *args):
})
def _get_devices(hass, discovery_type, keys, proxy):
def _get_devices(hass, discovery_type, keys, interface):
"""Get the HomeMatic devices for given discovery_type."""
device_arr = []
for key in keys:
device = hass.data[DATA_HOMEMATIC].devices[proxy][key]
device = hass.data[DATA_HOMEMATIC].devices[interface][key]
class_name = device.__class__.__name__
metadata = {}
@ -485,7 +524,7 @@ def _get_devices(hass, discovery_type, keys, proxy):
device_dict = {
CONF_PLATFORM: "homematic",
ATTR_ADDRESS: key,
ATTR_PROXY: proxy,
ATTR_INTERFACE: interface,
ATTR_NAME: name,
ATTR_CHANNEL: channel
}
@ -521,12 +560,12 @@ def _create_ha_name(name, channel, param, count):
return "{} {} {}".format(name, channel, param)
def _hm_event_handler(hass, proxy, device, caller, attribute, value):
def _hm_event_handler(hass, interface, device, caller, attribute, value):
"""Handle all pyhomematic device events."""
try:
channel = int(device.split(":")[1])
address = device.split(":")[0]
hmdevice = hass.data[DATA_HOMEMATIC].devices[proxy].get(address)
hmdevice = hass.data[DATA_HOMEMATIC].devices[interface].get(address)
except (TypeError, ValueError):
_LOGGER.error("Event handling channel convert error!")
return
@ -561,14 +600,14 @@ def _hm_event_handler(hass, proxy, device, caller, attribute, value):
def _device_from_servicecall(hass, service):
"""Extract HomeMatic device from service call."""
address = service.data.get(ATTR_ADDRESS)
proxy = service.data.get(ATTR_PROXY)
interface = service.data.get(ATTR_INTERFACE)
if address == 'BIDCOS-RF':
address = 'BidCoS-RF'
if proxy:
return hass.data[DATA_HOMEMATIC].devices[proxy].get(address)
if interface:
return hass.data[DATA_HOMEMATIC].devices[interface].get(address)
for _, devices in hass.data[DATA_HOMEMATIC].devices.items():
for devices in hass.data[DATA_HOMEMATIC].devices.values():
if address in devices:
return devices[address]
@ -576,25 +615,23 @@ def _device_from_servicecall(hass, service):
class HMHub(Entity):
"""The HomeMatic hub. (CCU2/HomeGear)."""
def __init__(self, hass, homematic, name, use_variables):
def __init__(self, hass, homematic, name):
"""Initialize HomeMatic hub."""
self.hass = hass
self.entity_id = "{}.{}".format(DOMAIN, name.lower())
self._homematic = homematic
self._variables = {}
self._name = name
self._state = STATE_UNKNOWN
self._use_variables = use_variables
self._state = None
# Load data
track_time_interval(
self.hass, self._update_hub, SCAN_INTERVAL_HUB)
self.hass.helpers.event.track_time_interval(
self._update_hub, SCAN_INTERVAL_HUB)
self.hass.add_job(self._update_hub, None)
if self._use_variables:
track_time_interval(
self.hass, self._update_variables, SCAN_INTERVAL_VARIABLES)
self.hass.add_job(self._update_variables, None)
self.hass.helpers.event.track_time_interval(
self._update_variables, SCAN_INTERVAL_VARIABLES)
self.hass.add_job(self._update_variables, None)
@property
def name(self):
@ -672,7 +709,7 @@ class HMDevice(Entity):
"""Initialize a generic HomeMatic device."""
self._name = config.get(ATTR_NAME)
self._address = config.get(ATTR_ADDRESS)
self._proxy = config.get(ATTR_PROXY)
self._interface = config.get(ATTR_INTERFACE)
self._channel = config.get(ATTR_CHANNEL)
self._state = config.get(ATTR_PARAM)
self._data = {}
@ -700,11 +737,6 @@ class HMDevice(Entity):
"""Return the name of the device."""
return self._name
@property
def assumed_state(self):
"""Return true if unable to access real state of the device."""
return not self._available
@property
def available(self):
"""Return true if device is available."""
@ -728,7 +760,7 @@ class HMDevice(Entity):
# Static attributes
attr['id'] = self._hmdevice.ADDRESS
attr['proxy'] = self._proxy
attr['interface'] = self._interface
return attr
@ -739,7 +771,8 @@ class HMDevice(Entity):
# Initialize
self._homematic = self.hass.data[DATA_HOMEMATIC]
self._hmdevice = self._homematic.devices[self._proxy][self._address]
self._hmdevice = \
self._homematic.devices[self._interface][self._address]
self._connected = True
try:

View file

@ -0,0 +1,68 @@
# Describes the format for available component services
virtualkey:
description: Press a virtual key from CCU/Homegear or simulate keypress.
fields:
address:
description: Address of homematic device or BidCoS-RF for virtual remote.
example: BidCoS-RF
channel:
description: Channel for calling a keypress.
example: 1
param:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT.
example: PRESS_LONG
interface:
description: (Optional) for set a interface value.
example: Interfaces name from config
set_variable_value:
description: Set the name of a node.
fields:
entity_id:
description: Name(s) of homematic central to set value.
example: 'homematic.ccu2'
name:
description: Name of the variable to set.
example: 'testvariable'
value:
description: New value
example: 1
set_device_value:
description: Set a device property on RPC XML interface.
fields:
address:
description: Address of homematic device or BidCoS-RF for virtual remote
example: BidCoS-RF
channel:
description: Channel for calling a keypress
example: 1
param:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT
example: PRESS_LONG
interface:
description: (Optional) for set a interface value
example: Interfaces name from config
value:
description: New value
example: 1
reconnect:
description: Reconnect to all Homematic Hubs.
set_install_mode:
description: Set a RPC XML interface into installation mode.
fields:
interface:
description: Select the given interface into install mode
example: Interfaces name from config
mode:
description: (Default 1) 1= Normal mode / 2= Remove exists old links
example: 1
time:
description: (Default 60) Time in seconds to run in install mode
example: 1
address:
description: (Optional) Address of homematic device or BidCoS-RF to learn
example: LEQ3948571

View file

@ -0,0 +1,244 @@
"""
This component provides basic support for the Philips Hue system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/hue/
"""
import json
import logging
import os
import socket
import voluptuous as vol
from homeassistant.components.discovery import SERVICE_HUE
from homeassistant.config import load_yaml_config_file
from homeassistant.const import CONF_FILENAME, CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
REQUIREMENTS = ['phue==1.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "hue"
SERVICE_HUE_SCENE = "hue_activate_scene"
CONF_BRIDGES = "bridges"
CONF_ALLOW_UNREACHABLE = 'allow_unreachable'
DEFAULT_ALLOW_UNREACHABLE = False
PHUE_CONFIG_FILE = 'phue.conf'
CONF_ALLOW_IN_EMULATED_HUE = "allow_in_emulated_hue"
DEFAULT_ALLOW_IN_EMULATED_HUE = True
CONF_ALLOW_HUE_GROUPS = "allow_hue_groups"
DEFAULT_ALLOW_HUE_GROUPS = True
BRIDGE_CONFIG_SCHEMA = vol.Schema([{
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_FILENAME, default=PHUE_CONFIG_FILE): cv.string,
vol.Optional(CONF_ALLOW_UNREACHABLE,
default=DEFAULT_ALLOW_UNREACHABLE): cv.boolean,
vol.Optional(CONF_ALLOW_IN_EMULATED_HUE,
default=DEFAULT_ALLOW_IN_EMULATED_HUE): cv.boolean,
vol.Optional(CONF_ALLOW_HUE_GROUPS,
default=DEFAULT_ALLOW_HUE_GROUPS): cv.boolean,
}])
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_BRIDGES, default=[]): BRIDGE_CONFIG_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)
ATTR_GROUP_NAME = "group_name"
ATTR_SCENE_NAME = "scene_name"
SCENE_SCHEMA = vol.Schema({
vol.Required(ATTR_GROUP_NAME): cv.string,
vol.Required(ATTR_SCENE_NAME): cv.string,
})
CONFIG_INSTRUCTIONS = """
Press the button on the bridge to register Philips Hue with Home Assistant.
![Location of button on bridge](/static/images/config_philips_hue.jpg)
"""
def setup(hass, config):
"""Set up the Hue platform."""
config = config.get(DOMAIN)
if config is None:
config = {}
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
discovery.listen(
hass,
SERVICE_HUE,
lambda service, discovery_info:
bridge_discovered(hass, service, discovery_info))
bridges = config.get(CONF_BRIDGES, [])
for bridge in bridges:
filename = bridge.get(CONF_FILENAME)
allow_unreachable = bridge.get(CONF_ALLOW_UNREACHABLE)
allow_in_emulated_hue = bridge.get(CONF_ALLOW_IN_EMULATED_HUE)
allow_hue_groups = bridge.get(CONF_ALLOW_HUE_GROUPS)
host = bridge.get(CONF_HOST)
if host is None:
host = _find_host_from_config(hass, filename)
if host is None:
_LOGGER.error("No host found in configuration")
return False
setup_bridge(host, hass, filename, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups)
return True
def bridge_discovered(hass, service, discovery_info):
"""Dispatcher for Hue discovery events."""
if "HASS Bridge" in discovery_info.get('name', ''):
return
host = discovery_info.get('host')
serial = discovery_info.get('serial')
filename = 'phue-{}.conf'.format(serial)
setup_bridge(host, hass, filename)
def setup_bridge(host, hass, filename=None, allow_unreachable=False,
allow_in_emulated_hue=True, allow_hue_groups=True):
"""Set up a given Hue bridge."""
# Only register a device once
if socket.gethostbyname(host) in hass.data[DOMAIN]:
return
bridge = HueBridge(host, hass, filename, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups)
bridge.setup()
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
"""Attempt to detect host based on existing configuration."""
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
try:
with open(path) as inp:
return next(iter(json.load(inp).keys()))
except (ValueError, AttributeError, StopIteration):
# ValueError if can't parse as JSON
# AttributeError if JSON value is not a dict
# StopIteration if no keys
return None
class HueBridge(object):
"""Manages a single Hue bridge."""
def __init__(self, host, hass, filename, allow_unreachable=False,
allow_in_emulated_hue=True, allow_hue_groups=True):
"""Initialize the system."""
self.host = host
self.hass = hass
self.filename = filename
self.allow_unreachable = allow_unreachable
self.allow_in_emulated_hue = allow_in_emulated_hue
self.allow_hue_groups = allow_hue_groups
self.bridge = None
self.configured = False
self.config_request_id = None
hass.data[DOMAIN][socket.gethostbyname(host)] = self
def setup(self):
"""Set up a phue bridge based on host parameter."""
import phue
try:
self.bridge = phue.Bridge(
self.host,
config_file_path=self.hass.config.path(self.filename))
except ConnectionRefusedError: # Wrong host was given
_LOGGER.error("Error connecting to the Hue bridge at %s",
self.host)
return
except phue.PhueRegistrationException:
_LOGGER.warning("Connected to Hue at %s but not registered.",
self.host)
self.request_configuration()
return
# If we came here and configuring this host, mark as done
if self.config_request_id:
request_id = self.config_request_id
self.config_request_id = None
configurator = self.hass.components.configurator
configurator.request_done(request_id)
self.configured = True
discovery.load_platform(
self.hass, 'light', DOMAIN,
{'bridge_id': socket.gethostbyname(self.host)})
# create a service for calling run_scene directly on the bridge,
# used to simplify automation rules.
def hue_activate_scene(call):
"""Service to call directly into bridge to set scenes."""
group_name = call.data[ATTR_GROUP_NAME]
scene_name = call.data[ATTR_SCENE_NAME]
self.bridge.run_scene(group_name, scene_name)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
self.hass.services.register(
DOMAIN, SERVICE_HUE_SCENE, hue_activate_scene,
descriptions.get(SERVICE_HUE_SCENE),
schema=SCENE_SCHEMA)
def request_configuration(self):
"""Request configuration steps from the user."""
configurator = self.hass.components.configurator
# We got an error if this method is called while we are configuring
if self.config_request_id:
configurator.notify_errors(
self.config_request_id,
"Failed to register, please try again.")
return
self.config_request_id = configurator.request_config(
"Philips Hue",
lambda data: self.setup(),
description=CONFIG_INSTRUCTIONS,
entity_picture="/static/images/logo_philips_hue.png",
submit_caption="I have pressed the button"
)
def get_api(self):
"""Return the full api dictionary from phue."""
return self.bridge.get_api()
def set_light(self, light_id, command):
"""Adjust properties of one or more lights. See phue for details."""
return self.bridge.set_light(light_id, command)
def set_group(self, light_id, command):
"""Change light settings for a group. See phue for detail."""
return self.bridge.set_group(light_id, command)

View file

@ -4,6 +4,7 @@ Support the ISY-994 controllers.
For configuration details please visit the documentation for this component at
https://home-assistant.io/components/isy994/
"""
import asyncio
from collections import namedtuple
import logging
from urllib.parse import urlparse
@ -17,7 +18,7 @@ from homeassistant.helpers import discovery, config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, Dict # noqa
REQUIREMENTS = ['PyISY==1.0.8']
REQUIREMENTS = ['PyISY==1.1.0']
_LOGGER = logging.getLogger(__name__)
@ -91,6 +92,34 @@ def filter_nodes(nodes: list, units: list=None, states: list=None) -> list:
return filtered_nodes
def _is_node_a_sensor(node, path: str, sensor_identifier: str) -> bool:
"""Determine if the given node is a sensor."""
if not isinstance(node, PYISY.Nodes.Node):
return False
if sensor_identifier in path or sensor_identifier in node.name:
return True
# This method is most reliable but only works on 5.x firmware
try:
if node.node_def_id == 'BinaryAlarm':
return True
except AttributeError:
pass
# This method works on all firmwares, but only for Insteon devices
try:
device_type = node.type
except AttributeError:
# Node has no type; most likely not an Insteon device
pass
else:
split_type = device_type.split('.')
return split_type[0] == '16' # 16 represents Insteon binary sensors
return False
def _categorize_nodes(hidden_identifier: str, sensor_identifier: str) -> None:
"""Categorize the ISY994 nodes."""
global SENSOR_NODES
@ -106,7 +135,7 @@ def _categorize_nodes(hidden_identifier: str, sensor_identifier: str) -> None:
hidden = hidden_identifier in path or hidden_identifier in node.name
if hidden:
node.name += hidden_identifier
if sensor_identifier in path or sensor_identifier in node.name:
if _is_node_a_sensor(node, path, sensor_identifier):
SENSOR_NODES.append(node)
elif isinstance(node, PYISY.Nodes.Node):
NODES.append(node)
@ -227,15 +256,31 @@ class ISYDevice(Entity):
def __init__(self, node) -> None:
"""Initialize the insteon device."""
self._node = node
self._change_handler = None
self._control_handler = None
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
self._change_handler = self._node.status.subscribe(
'changed', self.on_update)
if hasattr(self._node, 'controlEvents'):
self._control_handler = self._node.controlEvents.subscribe(
self.on_control)
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
self.schedule_update_ha_state()
def on_control(self, event: object) -> None:
"""Handle a control event from the ISY994 Node."""
self.hass.bus.fire('isy994_control', {
'entity_id': self.entity_id,
'control': event
})
@property
def domain(self) -> str:
"""Get the domain of the device."""
@ -270,6 +315,21 @@ class ISYDevice(Entity):
# pylint: disable=protected-access
return self._node.status._val
def is_unknown(self) -> bool:
"""Get whether or not the value of this Entity's node is unknown.
PyISY reports unknown values as -inf
"""
return self.value == -1 * float('inf')
@property
def state(self):
"""Return the state of the ISY device."""
if self.is_unknown():
return None
else:
return super().state
@property
def device_state_attributes(self) -> Dict:
"""Get the state attributes for the device."""

View file

@ -21,7 +21,7 @@ REQUIREMENTS = ['evdev==0.6.1']
_LOGGER = logging.getLogger(__name__)
DEVICE_DESCRIPTOR = 'device_descriptor'
DEVICE_ID_GROUP = 'Device descriptor or name'
DEVICE_ID_GROUP = 'Device description'
DEVICE_NAME = 'device_name'
DOMAIN = 'keyboard_remote'
@ -36,12 +36,13 @@ KEYBOARD_REMOTE_DISCONNECTED = 'keyboard_remote_disconnected'
TYPE = 'type'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Exclusive(DEVICE_DESCRIPTOR, DEVICE_ID_GROUP): cv.string,
vol.Exclusive(DEVICE_NAME, DEVICE_ID_GROUP): cv.string,
vol.Optional(TYPE, default='key_up'):
vol.All(cv.string, vol.Any('key_up', 'key_down', 'key_hold')),
}),
DOMAIN:
vol.All(cv.ensure_list, [vol.Schema({
vol.Exclusive(DEVICE_DESCRIPTOR, DEVICE_ID_GROUP): cv.string,
vol.Exclusive(DEVICE_NAME, DEVICE_ID_GROUP): cv.string,
vol.Optional(TYPE, default='key_up'):
vol.All(cv.string, vol.Any('key_up', 'key_down', 'key_hold'))
})])
}, extra=vol.ALLOW_EXTRA)
@ -49,11 +50,6 @@ def setup(hass, config):
"""Set up the keyboard_remote."""
config = config.get(DOMAIN)
if not config.get(DEVICE_DESCRIPTOR) and\
not config.get(DEVICE_NAME):
_LOGGER.error("No device_descriptor or device_name found")
return
keyboard_remote = KeyboardRemote(
hass,
config
@ -63,7 +59,7 @@ def setup(hass, config):
keyboard_remote.run()
def _stop_keyboard_remote(_event):
keyboard_remote.stopped.set()
keyboard_remote.stop()
hass.bus.listen_once(
EVENT_HOMEASSISTANT_START,
@ -77,19 +73,21 @@ def setup(hass, config):
return True
class KeyboardRemote(threading.Thread):
class KeyboardRemoteThread(threading.Thread):
"""This interfaces with the inputdevice using evdev."""
def __init__(self, hass, config):
"""Construct a KeyboardRemote interface object."""
from evdev import InputDevice, list_devices
def __init__(self, hass, device_name, device_descriptor, key_value):
"""Construct a thread listening for events on one device."""
self.hass = hass
self.device_name = device_name
self.device_descriptor = device_descriptor
self.key_value = key_value
self.device_descriptor = config.get(DEVICE_DESCRIPTOR)
self.device_name = config.get(DEVICE_NAME)
if self.device_descriptor:
self.device_id = self.device_descriptor
else:
self.device_id = self.device_name
self.dev = self._get_keyboard_device()
if self.dev is not None:
_LOGGER.debug("Keyboard connected, %s", self.device_id)
@ -103,6 +101,7 @@ class KeyboardRemote(threading.Thread):
id_folder = '/dev/input/by-id/'
if os.path.isdir(id_folder):
from evdev import InputDevice, list_devices
device_names = [InputDevice(file_name).name
for file_name in list_devices()]
_LOGGER.debug(
@ -116,7 +115,6 @@ class KeyboardRemote(threading.Thread):
threading.Thread.__init__(self)
self.stopped = threading.Event()
self.hass = hass
self.key_value = KEY_VALUE.get(config.get(TYPE, 'key_up'))
def _get_keyboard_device(self):
"""Get the keyboard device."""
@ -145,7 +143,7 @@ class KeyboardRemote(threading.Thread):
while not self.stopped.isSet():
# Sleeps to ease load on processor
time.sleep(.1)
time.sleep(.05)
if self.dev is None:
self.dev = self._get_keyboard_device()
@ -178,3 +176,32 @@ class KeyboardRemote(threading.Thread):
KEYBOARD_REMOTE_COMMAND_RECEIVED,
{KEY_CODE: event.code}
)
class KeyboardRemote(object):
"""Sets up one thread per device."""
def __init__(self, hass, config):
"""Construct a KeyboardRemote interface object."""
self.threads = []
for dev_block in config:
device_descriptor = dev_block.get(DEVICE_DESCRIPTOR)
device_name = dev_block.get(DEVICE_NAME)
key_value = KEY_VALUE.get(dev_block.get(TYPE, 'key_up'))
if device_descriptor is not None\
or device_name is not None:
thread = KeyboardRemoteThread(hass, device_name,
device_descriptor,
key_value)
self.threads.append(thread)
def run(self):
"""Run all event listener threads."""
for thread in self.threads:
thread.start()
def stop(self):
"""Stop all event listener threads."""
for thread in self.threads:
thread.stopped.set()

View file

@ -0,0 +1,117 @@
"""
Support for ADS light sources.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/light.ads/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.light import Light, ATTR_BRIGHTNESS, \
SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR, \
CONF_ADS_VAR_BRIGHTNESS
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS Light'
CONF_ADSVAR_BRIGHTNESS = 'adsvar_brightness'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_ADS_VAR_BRIGHTNESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the light platform for ADS."""
ads_hub = hass.data.get(DATA_ADS)
ads_var_enable = config.get(CONF_ADS_VAR)
ads_var_brightness = config.get(CONF_ADS_VAR_BRIGHTNESS)
name = config.get(CONF_NAME)
add_devices([AdsLight(ads_hub, ads_var_enable, ads_var_brightness,
name)], True)
class AdsLight(Light):
"""Representation of ADS light."""
def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name):
"""Initialize AdsLight entity."""
self._ads_hub = ads_hub
self._on_state = False
self._brightness = None
self._name = name
self.ads_var_enable = ads_var_enable
self.ads_var_brightness = ads_var_brightness
@asyncio.coroutine
def async_added_to_hass(self):
"""Register device notification."""
def update_on_state(name, value):
"""Handle device notifications for state."""
_LOGGER.debug('Variable %s changed its value to %d', name, value)
self._on_state = value
self.schedule_update_ha_state()
def update_brightness(name, value):
"""Handle device notification for brightness."""
_LOGGER.debug('Variable %s changed its value to %d', name, value)
self._brightness = value
self.schedule_update_ha_state()
self.hass.async_add_job(
self._ads_hub.add_device_notification,
self.ads_var_enable, self._ads_hub.PLCTYPE_BOOL, update_on_state
)
self.hass.async_add_job(
self._ads_hub.add_device_notification,
self.ads_var_brightness, self._ads_hub.PLCTYPE_INT,
update_brightness
)
@property
def name(self):
"""Return the name of the device if any."""
return self._name
@property
def brightness(self):
"""Return the brightness of the light (0..255)."""
return self._brightness
@property
def is_on(self):
"""Return if light is on."""
return self._on_state
@property
def should_poll(self):
"""Return False because entity pushes its state to HA."""
return False
@property
def supported_features(self):
"""Flag supported features."""
if self.ads_var_brightness is not None:
return SUPPORT_BRIGHTNESS
def turn_on(self, **kwargs):
"""Turn the light on or set a specific dimmer value."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
self._ads_hub.write_by_name(self.ads_var_enable, True,
self._ads_hub.PLCTYPE_BOOL)
if self.ads_var_brightness is not None and brightness is not None:
self._ads_hub.write_by_name(self.ads_var_brightness, brightness,
self._ads_hub.PLCTYPE_UINT)
def turn_off(self, **kwargs):
"""Turn the light off."""
self._ads_hub.write_by_name(self.ads_var_enable, False,
self._ads_hub.PLCTYPE_BOOL)

View file

@ -1,19 +1,21 @@
"""
Support for Hue lights.
This component provides light support for the Philips Hue system.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.hue/
"""
import json
import logging
import os
import random
import socket
from datetime import timedelta
import logging
import random
import re
import socket
import voluptuous as vol
import homeassistant.components.hue as hue
import homeassistant.util as util
from homeassistant.util import yaml
import homeassistant.util.color as color_util
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR,
@ -21,30 +23,21 @@ from homeassistant.components.light import (
FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION,
SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME)
from homeassistant.const import CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME
from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE_HIDDEN
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['phue==1.0']
DEPENDENCIES = ['hue']
# Track previously setup bridges
_CONFIGURED_BRIDGES = {}
# Map ip to request id for configuring
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
CONF_ALLOW_UNREACHABLE = 'allow_unreachable'
DEFAULT_ALLOW_UNREACHABLE = False
DOMAIN = "light"
SERVICE_HUE_SCENE = "hue_activate_scene"
DATA_KEY = 'hue_lights'
DATA_LIGHTS = 'lights'
DATA_LIGHTGROUPS = 'lightgroups'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
PHUE_CONFIG_FILE = 'phue.conf'
SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION)
SUPPORT_HUE_DIMMABLE = (SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS)
SUPPORT_HUE_COLOR_TEMP = (SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP)
@ -60,10 +53,14 @@ SUPPORT_HUE = {
'Color temperature light': SUPPORT_HUE_COLOR_TEMP
}
CONF_ALLOW_IN_EMULATED_HUE = "allow_in_emulated_hue"
DEFAULT_ALLOW_IN_EMULATED_HUE = True
ATTR_IS_HUE_GROUP = 'is_hue_group'
CONF_ALLOW_HUE_GROUPS = "allow_hue_groups"
# Legacy configuration, will be removed in 0.60
CONF_ALLOW_UNREACHABLE = 'allow_unreachable'
DEFAULT_ALLOW_UNREACHABLE = False
CONF_ALLOW_IN_EMULATED_HUE = 'allow_in_emulated_hue'
DEFAULT_ALLOW_IN_EMULATED_HUE = True
CONF_ALLOW_HUE_GROUPS = 'allow_hue_groups'
DEFAULT_ALLOW_HUE_GROUPS = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -75,236 +72,168 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
default=DEFAULT_ALLOW_HUE_GROUPS): cv.boolean,
})
ATTR_GROUP_NAME = "group_name"
ATTR_SCENE_NAME = "scene_name"
SCENE_SCHEMA = vol.Schema({
vol.Required(ATTR_GROUP_NAME): cv.string,
vol.Required(ATTR_SCENE_NAME): cv.string,
})
MIGRATION_ID = 'light_hue_config_migration'
MIGRATION_TITLE = 'Philips Hue Configuration Migration'
MIGRATION_INSTRUCTIONS = """
Configuration for the Philips Hue component has changed; action required.
ATTR_IS_HUE_GROUP = "is_hue_group"
You have configured at least one bridge:
CONFIG_INSTRUCTIONS = """
Press the button on the bridge to register Philips Hue with Home Assistant.
hue:
{config}
![Location of button on bridge](/static/images/config_philips_hue.jpg)
This configuration is deprecated, please check the
[Hue component](https://home-assistant.io/components/hue/) page for more
information.
"""
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
"""Attempt to detect host based on existing configuration."""
path = hass.config.path(filename)
if not os.path.isfile(path):
return None
try:
with open(path) as inp:
return next(json.loads(''.join(inp)).keys().__iter__())
except (ValueError, AttributeError, StopIteration):
# ValueError if can't parse as JSON
# AttributeError if JSON value is not a dict
# StopIteration if no keys
return None
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Hue lights."""
# Default needed in case of discovery
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
allow_unreachable = config.get(CONF_ALLOW_UNREACHABLE,
DEFAULT_ALLOW_UNREACHABLE)
allow_in_emulated_hue = config.get(CONF_ALLOW_IN_EMULATED_HUE,
DEFAULT_ALLOW_IN_EMULATED_HUE)
allow_hue_groups = config.get(CONF_ALLOW_HUE_GROUPS)
if discovery_info is not None:
if "HASS Bridge" in discovery_info.get('name', ''):
_LOGGER.info("Emulated hue found, will not add")
return False
host = discovery_info.get('host')
else:
host = config.get(CONF_HOST, None)
if host is None:
host = _find_host_from_config(hass, filename)
if host is None:
_LOGGER.error("No host found in configuration")
return False
# Only act if we are not already configuring this host
if host in _CONFIGURING or \
socket.gethostbyname(host) in _CONFIGURED_BRIDGES:
if discovery_info is None or 'bridge_id' not in discovery_info:
return
setup_bridge(host, hass, add_devices, filename, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups)
setup_data(hass)
if config is not None and len(config) > 0:
# Legacy configuration, will be removed in 0.60
config_str = yaml.dump([config])
# Indent so it renders in a fixed-width font
config_str = re.sub('(?m)^', ' ', config_str)
hass.components.persistent_notification.async_create(
MIGRATION_INSTRUCTIONS.format(config=config_str),
title=MIGRATION_TITLE,
notification_id=MIGRATION_ID)
bridge_id = discovery_info['bridge_id']
bridge = hass.data[hue.DOMAIN][bridge_id]
unthrottled_update_lights(hass, bridge, add_devices)
def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups):
"""Set up a phue bridge based on host parameter."""
def setup_data(hass):
"""Initialize internal data. Useful from tests."""
if DATA_KEY not in hass.data:
hass.data[DATA_KEY] = {DATA_LIGHTS: {}, DATA_LIGHTGROUPS: {}}
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights(hass, bridge, add_devices):
"""Update the Hue light objects with latest info from the bridge."""
return unthrottled_update_lights(hass, bridge, add_devices)
def unthrottled_update_lights(hass, bridge, add_devices):
"""Internal version of update_lights."""
import phue
if not bridge.configured:
return
try:
bridge = phue.Bridge(
host,
config_file_path=hass.config.path(filename))
except ConnectionRefusedError: # Wrong host was given
_LOGGER.error("Error connecting to the Hue bridge at %s", host)
api = bridge.get_api()
except phue.PhueRequestTimeout:
_LOGGER.warning('Timeout trying to reach the bridge')
return
except ConnectionRefusedError:
_LOGGER.error('The bridge refused the connection')
return
except socket.error:
# socket.error when we cannot reach Hue
_LOGGER.exception('Cannot reach the bridge')
return
except phue.PhueRegistrationException:
_LOGGER.warning("Connected to Hue at %s but not registered.", host)
bridge_type = get_bridge_type(api)
request_configuration(host, hass, add_devices, filename,
allow_unreachable, allow_in_emulated_hue,
allow_hue_groups)
new_lights = process_lights(
hass, api, bridge, bridge_type,
lambda **kw: update_lights(hass, bridge, add_devices, **kw))
if bridge.allow_hue_groups:
new_lightgroups = process_groups(
hass, api, bridge, bridge_type,
lambda **kw: update_lights(hass, bridge, add_devices, **kw))
new_lights.extend(new_lightgroups)
return
if new_lights:
add_devices(new_lights)
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = hass.components.configurator
configurator.request_done(request_id)
lights = {}
lightgroups = {}
skip_groups = not allow_hue_groups
def get_bridge_type(api):
"""Return the bridge type."""
api_name = api.get('config').get('name')
if api_name in ('RaspBee-GW', 'deCONZ-GW'):
return 'deconz'
else:
return 'hue'
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights():
"""Update the Hue light objects with latest info from the bridge."""
nonlocal skip_groups
try:
api = bridge.get_api()
except phue.PhueRequestTimeout:
_LOGGER.warning("Timeout trying to reach the bridge")
return
except ConnectionRefusedError:
_LOGGER.error("The bridge refused the connection")
return
except socket.error:
# socket.error when we cannot reach Hue
_LOGGER.exception("Cannot reach the bridge")
return
def process_lights(hass, api, bridge, bridge_type, update_lights_cb):
"""Set up HueLight objects for all lights."""
api_lights = api.get('lights')
api_lights = api.get('lights')
if not isinstance(api_lights, dict):
_LOGGER.error('Got unexpected result from Hue API')
return []
if not isinstance(api_lights, dict):
_LOGGER.error("Got unexpected result from Hue API")
return
new_lights = []
if skip_groups:
api_groups = {}
lights = hass.data[DATA_KEY][DATA_LIGHTS]
for light_id, info in api_lights.items():
if light_id not in lights:
lights[light_id] = HueLight(
int(light_id), info, bridge,
update_lights_cb,
bridge_type, bridge.allow_unreachable,
bridge.allow_in_emulated_hue)
new_lights.append(lights[light_id])
else:
api_groups = api.get('groups')
lights[light_id].info = info
lights[light_id].schedule_update_ha_state()
if not isinstance(api_groups, dict):
_LOGGER.error("Got unexpected result from Hue API")
return
return new_lights
new_lights = []
api_name = api.get('config').get('name')
if api_name in ('RaspBee-GW', 'deCONZ-GW'):
bridge_type = 'deconz'
def process_groups(hass, api, bridge, bridge_type, update_lights_cb):
"""Set up HueLight objects for all groups."""
api_groups = api.get('groups')
if not isinstance(api_groups, dict):
_LOGGER.error('Got unexpected result from Hue API')
return []
new_lights = []
groups = hass.data[DATA_KEY][DATA_LIGHTGROUPS]
for lightgroup_id, info in api_groups.items():
if 'state' not in info:
_LOGGER.warning('Group info does not contain state. '
'Please update your hub.')
return []
if lightgroup_id not in groups:
groups[lightgroup_id] = HueLight(
int(lightgroup_id), info, bridge,
update_lights_cb,
bridge_type, bridge.allow_unreachable,
bridge.allow_in_emulated_hue, True)
new_lights.append(groups[lightgroup_id])
else:
bridge_type = 'hue'
groups[lightgroup_id].info = info
groups[lightgroup_id].schedule_update_ha_state()
for light_id, info in api_lights.items():
if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights,
bridge_type, allow_unreachable,
allow_in_emulated_hue)
new_lights.append(lights[light_id])
else:
lights[light_id].info = info
lights[light_id].schedule_update_ha_state()
for lightgroup_id, info in api_groups.items():
if 'state' not in info:
_LOGGER.warning("Group info does not contain state. "
"Please update your hub.")
skip_groups = True
break
if lightgroup_id not in lightgroups:
lightgroups[lightgroup_id] = HueLight(
int(lightgroup_id), info, bridge, update_lights,
bridge_type, allow_unreachable, allow_in_emulated_hue,
True)
new_lights.append(lightgroups[lightgroup_id])
else:
lightgroups[lightgroup_id].info = info
lightgroups[lightgroup_id].schedule_update_ha_state()
if new_lights:
add_devices(new_lights)
_CONFIGURED_BRIDGES[socket.gethostbyname(host)] = True
# create a service for calling run_scene directly on the bridge,
# used to simplify automation rules.
def hue_activate_scene(call):
"""Service to call directly into bridge to set scenes."""
group_name = call.data[ATTR_GROUP_NAME]
scene_name = call.data[ATTR_SCENE_NAME]
bridge.run_scene(group_name, scene_name)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_HUE_SCENE, hue_activate_scene,
descriptions.get(SERVICE_HUE_SCENE),
schema=SCENE_SCHEMA)
update_lights()
def request_configuration(host, hass, add_devices, filename,
allow_unreachable, allow_in_emulated_hue,
allow_hue_groups):
"""Request configuration steps from the user."""
configurator = hass.components.configurator
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[host], "Failed to register, please try again.")
return
# pylint: disable=unused-argument
def hue_configuration_callback(data):
"""Set up actions to do when our configuration callback is called."""
setup_bridge(host, hass, add_devices, filename, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups)
_CONFIGURING[host] = configurator.request_config(
"Philips Hue", hue_configuration_callback,
description=CONFIG_INSTRUCTIONS,
entity_picture="/static/images/logo_philips_hue.png",
submit_caption="I have pressed the button"
)
return new_lights
class HueLight(Light):
"""Representation of a Hue light."""
def __init__(self, light_id, info, bridge, update_lights,
def __init__(self, light_id, info, bridge, update_lights_cb,
bridge_type, allow_unreachable, allow_in_emulated_hue,
is_group=False):
"""Initialize the light."""
self.light_id = light_id
self.info = info
self.bridge = bridge
self.update_lights = update_lights
self.update_lights = update_lights_cb
self.bridge_type = bridge_type
self.allow_unreachable = allow_unreachable
self.is_group = is_group
@ -381,14 +310,15 @@ class HueLight(Light):
command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)
if ATTR_XY_COLOR in kwargs:
if self.info.get('manufacturername') == "OSRAM":
hue, sat = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
command['hue'] = hue
if self.info.get('manufacturername') == 'OSRAM':
color_hue, sat = color_util.color_xy_to_hs(
*kwargs[ATTR_XY_COLOR])
command['hue'] = color_hue
command['sat'] = sat
else:
command['xy'] = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
if self.info.get('manufacturername') == "OSRAM":
if self.info.get('manufacturername') == 'OSRAM':
hsv = color_util.color_RGB_to_hsv(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['hue'] = hsv[0]

View file

@ -33,7 +33,7 @@ import homeassistant.util.color as color_util
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['aiolifx==0.6.0', 'aiolifx_effects==0.1.2']
REQUIREMENTS = ['aiolifx==0.6.1', 'aiolifx_effects==0.1.2']
UDP_BROADCAST_PORT = 56700
@ -157,20 +157,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return True
def lifxwhite(device):
"""Return whether this is a white-only bulb."""
features = aiolifx().products.features_map.get(device.product, None)
if features:
return not features["color"]
return False
def lifxmultizone(device):
"""Return whether this is a multizone bulb/strip."""
features = aiolifx().products.features_map.get(device.product, None)
if features:
return features["multizone"]
return False
def lifx_features(device):
"""Return a feature map for this device, or a default map if unknown."""
return aiolifx().products.features_map.get(device.product) or \
aiolifx().products.features_map.get(1)
def find_hsbk(**kwargs):
@ -342,12 +332,12 @@ class LIFXManager(object):
device.retry_count = MESSAGE_RETRIES
device.unregister_timeout = UNAVAILABLE_GRACE
if lifxwhite(device):
entity = LIFXWhite(device, self.effects_conductor)
elif lifxmultizone(device):
if lifx_features(device)["multizone"]:
entity = LIFXStrip(device, self.effects_conductor)
else:
elif lifx_features(device)["color"]:
entity = LIFXColor(device, self.effects_conductor)
else:
entity = LIFXWhite(device, self.effects_conductor)
_LOGGER.debug("%s register READY", entity.who)
self.entities[device.mac_addr] = entity
@ -427,6 +417,29 @@ class LIFXLight(Light):
"""Return a string identifying the device."""
return "%s (%s)" % (self.device.ip_addr, self.name)
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
kelvin = lifx_features(self.device)['max_kelvin']
return math.floor(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
kelvin = lifx_features(self.device)['min_kelvin']
return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin))
@property
def supported_features(self):
"""Flag supported features."""
support = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_EFFECT
device_features = lifx_features(self.device)
if device_features['min_kelvin'] != device_features['max_kelvin']:
support |= SUPPORT_COLOR_TEMP
return support
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
@ -571,22 +584,6 @@ class LIFXLight(Light):
class LIFXWhite(LIFXLight):
"""Representation of a white-only LIFX light."""
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
return math.floor(color_util.color_temperature_kelvin_to_mired(6500))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
return math.ceil(color_util.color_temperature_kelvin_to_mired(2700))
@property
def supported_features(self):
"""Flag supported features."""
return (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_TRANSITION |
SUPPORT_EFFECT)
@property
def effect_list(self):
"""Return the list of supported effects for this light."""
@ -599,21 +596,12 @@ class LIFXWhite(LIFXLight):
class LIFXColor(LIFXLight):
"""Representation of a color LIFX light."""
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
return math.floor(color_util.color_temperature_kelvin_to_mired(9000))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
return math.ceil(color_util.color_temperature_kelvin_to_mired(2500))
@property
def supported_features(self):
"""Flag supported features."""
return (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_TRANSITION |
SUPPORT_EFFECT | SUPPORT_RGB_COLOR | SUPPORT_XY_COLOR)
support = super().supported_features
support |= SUPPORT_RGB_COLOR | SUPPORT_XY_COLOR
return support
@property
def effect_list(self):

View file

@ -12,13 +12,15 @@ import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA)
from homeassistant.components import mochad
from homeassistant.const import (CONF_NAME, CONF_PLATFORM, CONF_DEVICES,
CONF_ADDRESS)
from homeassistant.const import (
CONF_NAME, CONF_PLATFORM, CONF_DEVICES, CONF_ADDRESS)
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['mochad']
_LOGGER = logging.getLogger(__name__)
CONF_BRIGHTNESS_LEVELS = 'brightness_levels'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PLATFORM): mochad.DOMAIN,
@ -26,6 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.x10_address,
vol.Optional(mochad.CONF_COMM_TYPE): cv.string,
vol.Optional(CONF_BRIGHTNESS_LEVELS, default=32):
vol.All(vol.Coerce(int), vol.In([32, 64, 256])),
}]
})
@ -54,6 +58,7 @@ class MochadLight(Light):
comm_type=self._comm_type)
self._brightness = 0
self._state = self._get_device_status()
self._brightness_levels = dev.get(CONF_BRIGHTNESS_LEVELS) - 1
@property
def brightness(self):
@ -62,7 +67,8 @@ class MochadLight(Light):
def _get_device_status(self):
"""Get the status of the light from mochad."""
status = self.device.get_status().rstrip()
with mochad.REQ_LOCK:
status = self.device.get_status().rstrip()
return status == 'on'
@property
@ -85,15 +91,47 @@ class MochadLight(Light):
"""X10 devices are normally 1-way so we have to assume the state."""
return True
def _calculate_brightness_value(self, value):
return int(value * (float(self._brightness_levels) / 255.0))
def _adjust_brightness(self, brightness):
if self._brightness > brightness:
bdelta = self._brightness - brightness
mochad_brightness = self._calculate_brightness_value(bdelta)
self.device.send_cmd("dim {}".format(mochad_brightness))
self._controller.read_data()
elif self._brightness < brightness:
bdelta = brightness - self._brightness
mochad_brightness = self._calculate_brightness_value(bdelta)
self.device.send_cmd("bright {}".format(mochad_brightness))
self._controller.read_data()
def turn_on(self, **kwargs):
"""Send the command to turn the light on."""
self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
self.device.send_cmd("xdim {}".format(self._brightness))
self._controller.read_data()
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
with mochad.REQ_LOCK:
if self._brightness_levels > 32:
out_brightness = self._calculate_brightness_value(brightness)
self.device.send_cmd('xdim {}'.format(out_brightness))
self._controller.read_data()
else:
self.device.send_cmd("on")
self._controller.read_data()
# There is no persistence for X10 modules so a fresh on command
# will be full brightness
if self._brightness == 0:
self._brightness = 255
self._adjust_brightness(brightness)
self._brightness = brightness
self._state = True
def turn_off(self, **kwargs):
"""Send the command to turn the light on."""
self.device.send_cmd('off')
self._controller.read_data()
with mochad.REQ_LOCK:
self.device.send_cmd('off')
self._controller.read_data()
# There is no persistence for X10 modules so we need to prepare
# to track a fresh on command will full brightness
if self._brightness_levels == 31:
self._brightness = 0
self._state = False

View file

@ -72,6 +72,7 @@ class TPLinkSmartBulb(Light):
if name is not None:
self._name = name
self._state = None
self._available = True
self._color_temp = None
self._brightness = None
self._rgb = None
@ -83,6 +84,11 @@ class TPLinkSmartBulb(Light):
"""Return the name of the Smart Bulb, if any."""
return self._name
@property
def available(self) -> bool:
"""Return if bulb is available."""
return self._available
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
@ -132,6 +138,7 @@ class TPLinkSmartBulb(Light):
"""Update the TP-Link Bulb's state."""
from pyHS100 import SmartDeviceException
try:
self._available = True
if self._supported_features == 0:
self.get_features()
self._state = (
@ -163,8 +170,10 @@ class TPLinkSmartBulb(Light):
except KeyError:
# device returned no daily/monthly history
pass
except (SmartDeviceException, OSError) as ex:
_LOGGER.warning('Could not read state for %s: %s', self._name, ex)
_LOGGER.warning("Could not read state for %s: %s", self._name, ex)
self._available = False
@property
def supported_features(self):

View file

@ -99,7 +99,7 @@ class TradfriGroup(Light):
@asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Instruct the group lights to turn off."""
self.hass.async_add_job(self._api(self._group.set_state(0)))
yield from self._api(self._group.set_state(0))
@asyncio.coroutine
def async_turn_on(self, **kwargs):
@ -112,10 +112,10 @@ class TradfriGroup(Light):
if kwargs[ATTR_BRIGHTNESS] == 255:
kwargs[ATTR_BRIGHTNESS] = 254
self.hass.async_add_job(self._api(
self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)))
yield from self._api(
self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys))
else:
self.hass.async_add_job(self._api(self._group.set_state(1)))
yield from self._api(self._group.set_state(1))
@callback
def _async_start_observe(self, exc=None):
@ -140,11 +140,11 @@ class TradfriGroup(Light):
self._group = group
self._name = group.name
@callback
def _observe_update(self, tradfri_device):
"""Receive new state data for this light."""
self._refresh(tradfri_device)
self.hass.async_add_job(self.async_update_ha_state())
self.async_schedule_update_ha_state()
class TradfriLight(Light):
@ -160,6 +160,7 @@ class TradfriLight(Light):
self._rgb_color = None
self._features = SUPPORTED_FEATURES
self._temp_supported = False
self._available = True
self._refresh(light)
@ -196,6 +197,11 @@ class TradfriLight(Light):
"""Start thread when added to hass."""
self._async_start_observe()
@property
def available(self):
"""Return True if entity is available."""
return self._available
@property
def should_poll(self):
"""No polling needed for tradfri light."""
@ -238,8 +244,7 @@ class TradfriLight(Light):
@asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Instruct the light to turn off."""
self.hass.async_add_job(self._api(
self._light_control.set_state(False)))
yield from self._api(self._light_control.set_state(False))
@asyncio.coroutine
def async_turn_on(self, **kwargs):
@ -250,17 +255,17 @@ class TradfriLight(Light):
for ATTR_RGB_COLOR, this also supports Philips Hue bulbs.
"""
if ATTR_RGB_COLOR in kwargs and self._light_data.hex_color is not None:
self.hass.async_add_job(self._api(
yield from self._api(
self._light.light_control.set_rgb_color(
*kwargs[ATTR_RGB_COLOR])))
*kwargs[ATTR_RGB_COLOR]))
elif ATTR_COLOR_TEMP in kwargs and \
self._light_data.hex_color is not None and \
self._temp_supported:
kelvin = color_util.color_temperature_mired_to_kelvin(
kwargs[ATTR_COLOR_TEMP])
self.hass.async_add_job(self._api(
self._light_control.set_kelvin_color(kelvin)))
yield from self._api(
self._light_control.set_kelvin_color(kelvin))
keys = {}
if ATTR_TRANSITION in kwargs:
@ -270,12 +275,12 @@ class TradfriLight(Light):
if kwargs[ATTR_BRIGHTNESS] == 255:
kwargs[ATTR_BRIGHTNESS] = 254
self.hass.async_add_job(self._api(
yield from self._api(
self._light_control.set_dimmer(kwargs[ATTR_BRIGHTNESS],
**keys)))
**keys))
else:
self.hass.async_add_job(self._api(
self._light_control.set_state(True)))
yield from self._api(
self._light_control.set_state(True))
@callback
def _async_start_observe(self, exc=None):
@ -300,6 +305,7 @@ class TradfriLight(Light):
self._light = light
# Caching of LightControl and light object
self._available = light.reachable
self._light_control = light.light_control
self._light_data = light.light_control.lights[0]
self._name = light.name
@ -318,10 +324,11 @@ class TradfriLight(Light):
self._temp_supported = self._light.device_info.manufacturer \
in ALLOWED_TEMPERATURES
@callback
def _observe_update(self, tradfri_device):
"""Receive new state data for this light."""
self._refresh(tradfri_device)
self._rgb_color = color_util.rgb_hex_to_rgb_list(
self._light_data.hex_color_inferred
)
self.hass.async_add_job(self.async_update_ha_state())
self.async_schedule_update_ha_state()

View file

@ -21,7 +21,8 @@ DEPENDENCIES = ['vera']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera lights."""
add_devices(
VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light'])
VeraLight(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['light'])
class VeraLight(VeraDevice, Light):

View file

@ -66,7 +66,10 @@ class ISYLockDevice(isy.ISYDevice, LockDevice):
@property
def state(self) -> str:
"""Get the state of the lock."""
return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN)
if self.is_unknown():
return None
else:
return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN)
def lock(self, **kwargs) -> None:
"""Send the lock command to the ISY994 device."""

View file

@ -19,8 +19,8 @@ DEPENDENCIES = ['vera']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Vera locks."""
add_devices(
VeraLock(device, VERA_CONTROLLER) for
device in VERA_DEVICES['lock'])
VeraLock(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['lock'])
class VeraLock(VeraDevice, LockDevice):

View file

@ -135,9 +135,8 @@ class LogbookView(HomeAssistantView):
hass = request.app['hass']
events = yield from hass.async_add_job(
_get_events, hass, start_day, end_day)
events = _exclude_events(events, self.config)
return self.json(humanify(events))
_get_events, hass, self.config, start_day, end_day)
return self.json(events)
class Entry(object):
@ -274,7 +273,7 @@ def humanify(events):
entity_id)
def _get_events(hass, start_day, end_day):
def _get_events(hass, config, start_day, end_day):
"""Get events for a period of time."""
from homeassistant.components.recorder.models import Events
from homeassistant.components.recorder.util import (
@ -285,7 +284,8 @@ def _get_events(hass, start_day, end_day):
Events.time_fired).filter(
(Events.time_fired > start_day) &
(Events.time_fired < end_day))
return execute(query)
events = execute(query)
return humanify(_exclude_events(events, config))
def _exclude_events(events, config):

View file

@ -16,7 +16,7 @@ from homeassistant.components.media_player import (
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['youtube_dl==2017.11.26']
REQUIREMENTS = ['youtube_dl==2017.12.10']
_LOGGER = logging.getLogger(__name__)

View file

@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['denonavr==0.5.4']
REQUIREMENTS = ['denonavr==0.5.5']
_LOGGER = logging.getLogger(__name__)
@ -102,12 +102,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if config.get(CONF_HOST) is None and discovery_info is None:
d_receivers = denonavr.discover()
# More than one receiver could be discovered by that method
if d_receivers is not None:
for d_receiver in d_receivers:
host = d_receiver["host"]
name = d_receiver["friendlyName"]
new_hosts.append(
NewHost(host=host, name=name))
for d_receiver in d_receivers:
host = d_receiver["host"]
name = d_receiver["friendlyName"]
new_hosts.append(
NewHost(host=host, name=name))
for entry in new_hosts:
# Check if host not in cache, append it and save for later

View file

@ -20,8 +20,9 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, STATE_ON, STATE_OFF, STATE_PLAYING,
STATE_PAUSED, CONF_NAME)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['liveboxplaytv==2.0.0']
REQUIREMENTS = ['liveboxplaytv==2.0.2', 'pyteleloisirs==3.3']
_LOGGER = logging.getLogger(__name__)
@ -76,19 +77,32 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
self._channel_list = {}
self._current_channel = None
self._current_program = None
self._media_duration = None
self._media_remaining_time = None
self._media_image_url = None
self._media_last_updated = None
@asyncio.coroutine
def async_update(self):
"""Retrieve the latest data."""
import pyteleloisirs
try:
self._state = self.refresh_state()
# Update current channel
channel = self._client.channel
if channel is not None:
self._current_program = yield from \
self._client.async_get_current_program_name()
self._current_channel = channel
program = yield from \
self._client.async_get_current_program()
if program and self._current_program != program.get('name'):
self._current_program = program.get('name')
# Media progress info
self._media_duration = \
pyteleloisirs.get_program_duration(program)
rtime = pyteleloisirs.get_remaining_time(program)
if rtime != self._media_remaining_time:
self._media_remaining_time = rtime
self._media_last_updated = dt_util.utcnow()
# Set media image to current program if a thumbnail is
# available. Otherwise we'll use the channel's image.
img_size = 800
@ -100,7 +114,6 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
chan_img_url = \
self._client.get_current_channel_image(img_size)
self._media_image_url = chan_img_url
self.refresh_channel_list()
except requests.ConnectionError:
self._state = None
@ -149,8 +162,25 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
if self._current_program:
return '{}: {}'.format(self._current_channel,
self._current_program)
else:
return self._current_channel
return self._current_channel
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self._media_duration
@property
def media_position(self):
"""Position of current playing media in seconds."""
return self._media_remaining_time
@property
def media_position_updated_at(self):
"""When was the position of the current playing media valid.
Returns value from homeassistant.util.dt.utcnow().
"""
return self._media_last_updated
@property
def supported_features(self):

View file

@ -6,6 +6,7 @@ https://home-assistant.io/components/media_player.samsungtv/
"""
import logging
import socket
from datetime import timedelta
import voluptuous as vol
@ -17,6 +18,7 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT,
CONF_MAC)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util
REQUIREMENTS = ['samsungctl==0.6.0', 'wakeonlan==0.2.2']
@ -100,6 +102,9 @@ class SamsungTVDevice(MediaPlayerDevice):
self._playing = True
self._state = STATE_UNKNOWN
self._remote = None
# Mark the end of a shutdown command (need to wait 15 seconds before
# sending the next command to avoid turning the TV back ON).
self._end_of_power_off = None
# Generate a configuration for the Samsung library
self._config = {
'name': 'HomeAssistant',
@ -118,7 +123,7 @@ class SamsungTVDevice(MediaPlayerDevice):
def update(self):
"""Retrieve the latest data."""
# Send an empty key to see if we are still connected
return self.send_key('KEY')
self.send_key('KEY')
def get_remote(self):
"""Create or return a remote control instance."""
@ -130,6 +135,10 @@ class SamsungTVDevice(MediaPlayerDevice):
def send_key(self, key):
"""Send a key to the tv and handles exceptions."""
if self._power_off_in_progress() \
and not (key == 'KEY_POWER' or key == 'KEY_POWEROFF'):
_LOGGER.info("TV is powering off, not sending command: %s", key)
return
try:
self.get_remote().control(key)
self._state = STATE_ON
@ -139,13 +148,16 @@ class SamsungTVDevice(MediaPlayerDevice):
# BrokenPipe can occur when the commands is sent to fast
self._state = STATE_ON
self._remote = None
return False
return
except (self._exceptions_class.ConnectionClosed, OSError):
self._state = STATE_OFF
self._remote = None
return False
if self._power_off_in_progress():
self._state = STATE_OFF
return True
def _power_off_in_progress(self):
return self._end_of_power_off is not None and \
self._end_of_power_off > dt_util.utcnow()
@property
def name(self):
@ -171,12 +183,17 @@ class SamsungTVDevice(MediaPlayerDevice):
def turn_off(self):
"""Turn off media player."""
self._end_of_power_off = dt_util.utcnow() + timedelta(seconds=15)
if self._config['method'] == 'websocket':
self.send_key('KEY_POWER')
else:
self.send_key('KEY_POWEROFF')
# Force closing of remote session to provide instant UI feedback
self.get_remote().close()
try:
self.get_remote().close()
except OSError:
_LOGGER.debug("Could not establish connection.")
def volume_up(self):
"""Volume up the media player."""

View file

@ -215,6 +215,18 @@ sonos_clear_sleep_timer:
description: Name(s) of entities that will have the timer cleared.
example: 'media_player.living_room_sonos'
sonos_set_option:
description: Set Sonos sound options.
fields:
entity_id:
description: Name(s) of entities that will have options set.
example: 'media_player.living_room_sonos'
night_sound:
description: Enable Night Sound mode
example: 'true'
speech_enhance:
description: Enable Speech Enhancement mode
example: 'true'
soundtouch_play_everywhere:
description: Play on all Bose Soundtouch devices.

View file

@ -19,7 +19,7 @@ from homeassistant.components.media_player import (
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_CLEAR_PLAYLIST,
SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_STOP,
SUPPORT_PLAY)
SUPPORT_PLAY, SUPPORT_SHUFFLE_SET)
from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_OFF, ATTR_ENTITY_ID,
CONF_HOSTS, ATTR_TIME)
@ -27,7 +27,7 @@ from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow
REQUIREMENTS = ['SoCo==0.12']
REQUIREMENTS = ['SoCo==0.13']
_LOGGER = logging.getLogger(__name__)
@ -43,7 +43,7 @@ _REQUESTS_LOGGER.setLevel(logging.ERROR)
SUPPORT_SONOS = SUPPORT_STOP | SUPPORT_PAUSE | SUPPORT_VOLUME_SET |\
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK |\
SUPPORT_PLAY_MEDIA | SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST |\
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET
SERVICE_JOIN = 'sonos_join'
SERVICE_UNJOIN = 'sonos_unjoin'
@ -52,6 +52,7 @@ SERVICE_RESTORE = 'sonos_restore'
SERVICE_SET_TIMER = 'sonos_set_sleep_timer'
SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer'
SERVICE_UPDATE_ALARM = 'sonos_update_alarm'
SERVICE_SET_OPTION = 'sonos_set_option'
DATA_SONOS = 'sonos'
@ -69,6 +70,8 @@ ATTR_ENABLED = 'enabled'
ATTR_INCLUDE_LINKED_ZONES = 'include_linked_zones'
ATTR_MASTER = 'master'
ATTR_WITH_GROUP = 'with_group'
ATTR_NIGHT_SOUND = 'night_sound'
ATTR_SPEECH_ENHANCE = 'speech_enhance'
ATTR_IS_COORDINATOR = 'is_coordinator'
@ -105,6 +108,11 @@ SONOS_UPDATE_ALARM_SCHEMA = SONOS_SCHEMA.extend({
vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean,
})
SONOS_SET_OPTION_SCHEMA = SONOS_SCHEMA.extend({
vol.Optional(ATTR_NIGHT_SOUND): cv.boolean,
vol.Optional(ATTR_SPEECH_ENHANCE): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Sonos platform."""
@ -140,7 +148,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hosts = hosts.split(',') if isinstance(hosts, str) else hosts
players = []
for host in hosts:
players.append(soco.SoCo(socket.gethostbyname(host)))
try:
players.append(soco.SoCo(socket.gethostbyname(host)))
except OSError:
_LOGGER.warning("Failed to initialize '%s'", host)
if not players:
players = soco.discover(
@ -189,6 +200,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device.clear_sleep_timer()
elif service.service == SERVICE_UPDATE_ALARM:
device.update_alarm(**service.data)
elif service.service == SERVICE_SET_OPTION:
device.update_option(**service.data)
device.schedule_update_ha_state(True)
@ -221,6 +234,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
descriptions.get(SERVICE_UPDATE_ALARM),
schema=SONOS_UPDATE_ALARM_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_SET_OPTION, service_handle,
descriptions.get(SERVICE_SET_OPTION),
schema=SONOS_SET_OPTION_SCHEMA)
def _parse_timespan(timespan):
"""Parse a time-span into number of seconds."""
@ -331,8 +349,11 @@ class SonosDevice(MediaPlayerDevice):
self._support_previous_track = False
self._support_next_track = False
self._support_play = False
self._support_shuffle_set = True
self._support_stop = False
self._support_pause = False
self._night_sound = None
self._speech_enhance = None
self._current_track_uri = None
self._current_track_is_radio_stream = False
self._queue = None
@ -450,8 +471,11 @@ class SonosDevice(MediaPlayerDevice):
self._support_previous_track = False
self._support_next_track = False
self._support_play = False
self._support_shuffle_set = False
self._support_stop = False
self._support_pause = False
self._night_sound = None
self._speech_enhance = None
self._is_playing_tv = False
self._is_playing_line_in = False
self._source_name = None
@ -524,6 +548,9 @@ class SonosDevice(MediaPlayerDevice):
media_position_updated_at = None
source_name = None
night_sound = self._player.night_mode
speech_enhance = self._player.dialog_mode
is_radio_stream = \
current_media_uri.startswith('x-sonosapi-stream:') or \
current_media_uri.startswith('x-rincon-mp3radio:')
@ -536,6 +563,7 @@ class SonosDevice(MediaPlayerDevice):
support_play = False
support_stop = True
support_pause = False
support_shuffle_set = False
if is_playing_tv:
media_artist = SUPPORT_SOURCE_TV
@ -558,6 +586,7 @@ class SonosDevice(MediaPlayerDevice):
support_play = True
support_stop = True
support_pause = False
support_shuffle_set = False
source_name = 'Radio'
# Check if currently playing radio station is in favorites
@ -622,6 +651,7 @@ class SonosDevice(MediaPlayerDevice):
support_play = True
support_stop = True
support_pause = True
support_shuffle_set = True
position_info = self._player.avTransport.GetPositionInfo(
[('InstanceID', 0),
@ -694,8 +724,11 @@ class SonosDevice(MediaPlayerDevice):
self._support_previous_track = support_previous_track
self._support_next_track = support_next_track
self._support_play = support_play
self._support_shuffle_set = support_shuffle_set
self._support_stop = support_stop
self._support_pause = support_pause
self._night_sound = night_sound
self._speech_enhance = speech_enhance
self._is_playing_tv = is_playing_tv
self._is_playing_line_in = is_playing_line_in
self._source_name = source_name
@ -762,6 +795,11 @@ class SonosDevice(MediaPlayerDevice):
"""Return true if volume is muted."""
return self._player_volume_muted
@property
def shuffle(self):
"""Shuffling state."""
return True if self._player.play_mode == 'SHUFFLE' else False
@property
def media_content_id(self):
"""Content ID of current playing media."""
@ -834,6 +872,16 @@ class SonosDevice(MediaPlayerDevice):
return self._media_title
@property
def night_sound(self):
"""Get status of Night Sound."""
return self._night_sound
@property
def speech_enhance(self):
"""Get status of Speech Enhancement."""
return self._speech_enhance
@property
def supported_features(self):
"""Flag media player features that are supported."""
@ -850,7 +898,8 @@ class SonosDevice(MediaPlayerDevice):
if not self._support_play:
supported = supported ^ SUPPORT_PLAY
if not self._support_shuffle_set:
supported = supported ^ SUPPORT_SHUFFLE_SET
if not self._support_stop:
supported = supported ^ SUPPORT_STOP
@ -874,6 +923,11 @@ class SonosDevice(MediaPlayerDevice):
"""Set volume level, range 0..1."""
self._player.volume = str(int(volume * 100))
@soco_error
def set_shuffle(self, shuffle):
"""Enable/Disable shuffle mode."""
self._player.play_mode = 'SHUFFLE' if shuffle else 'NORMAL'
@soco_error
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
@ -932,7 +986,6 @@ class SonosDevice(MediaPlayerDevice):
self._player.stop()
self._player.clear_queue()
self._player.play_mode = 'NORMAL'
self._player.add_to_queue(didl)
@property
@ -1160,7 +1213,24 @@ class SonosDevice(MediaPlayerDevice):
a.include_linked_zones = data[ATTR_INCLUDE_LINKED_ZONES]
a.save()
@soco_error
def update_option(self, **data):
"""Modify playback options."""
if ATTR_NIGHT_SOUND in data and self.night_sound is not None:
self.soco.night_mode = data[ATTR_NIGHT_SOUND]
if ATTR_SPEECH_ENHANCE in data and self.speech_enhance is not None:
self.soco.dialog_mode = data[ATTR_SPEECH_ENHANCE]
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {ATTR_IS_COORDINATOR: self.is_coordinator}
attributes = {ATTR_IS_COORDINATOR: self.is_coordinator}
if self.night_sound is not None:
attributes[ATTR_NIGHT_SOUND] = self.night_sound
if self.speech_enhance is not None:
attributes[ATTR_SPEECH_ENHANCE] = self.speech_enhance
return attributes

View file

@ -0,0 +1,207 @@
"""
Support for Logitech UE Smart Radios.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.ue_smart_radio/
"""
import logging
import voluptuous as vol
import requests
from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA,
SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, STATE_OFF, STATE_IDLE, STATE_PLAYING,
STATE_PAUSED)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
ICON = "mdi:radio"
URL = "http://decibel.logitechmusic.com/jsonrpc.js"
SUPPORT_UE_SMART_RADIO = SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_TURN_ON | \
SUPPORT_TURN_OFF | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE
PLAYBACK_DICT = {"play": STATE_PLAYING,
"pause": STATE_PAUSED,
"stop": STATE_IDLE}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def send_request(payload, session):
"""Send request to radio."""
try:
request = requests.post(URL,
cookies={"sdi_squeezenetwork_session":
session},
json=payload, timeout=5)
except requests.exceptions.Timeout:
_LOGGER.error("Timed out when sending request")
except requests.exceptions.ConnectionError:
_LOGGER.error("An error occurred while connecting")
else:
return request.json()
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Logitech UE Smart Radio platform."""
email = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
session_request = requests.post("https://www.uesmartradio.com/user/login",
data={"email": email, "password":
password})
session = session_request.cookies["sdi_squeezenetwork_session"]
player_request = send_request({"params": ["", ["serverstatus"]]}, session)
player_id = player_request["result"]["players_loop"][0]["playerid"]
player_name = player_request["result"]["players_loop"][0]["name"]
add_devices([UERadioDevice(session, player_id, player_name)])
class UERadioDevice(MediaPlayerDevice):
"""Representation of a Logitech UE Smart Radio device."""
def __init__(self, session, player_id, player_name):
"""Initialize the Logitech UE Smart Radio device."""
self._session = session
self._player_id = player_id
self._name = player_name
self._state = None
self._volume = 0
self._last_volume = 0
self._media_title = None
self._media_artist = None
self._media_artwork_url = None
def send_command(self, command):
"""Send command to radio."""
send_request({"method": "slim.request", "params":
[self._player_id, command]}, self._session)
def update(self):
"""Get the latest details from the device."""
request = send_request({
"method": "slim.request", "params":
[self._player_id, ["status", "-", 1,
"tags:cgABbehldiqtyrSuoKLN"]]}, self._session)
if request["error"] is not None:
self._state = None
return
if request["result"]["power"] == 0:
self._state = STATE_OFF
else:
self._state = PLAYBACK_DICT[request["result"]["mode"]]
media_info = request["result"]["playlist_loop"][0]
self._volume = request["result"]["mixer volume"] / 100
self._media_artwork_url = media_info["artwork_url"]
self._media_title = media_info["title"]
if "artist" in media_info:
self._media_artist = media_info["artist"]
else:
self._media_artist = media_info.get("remote_title")
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return ICON
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return True if self._volume <= 0 else False
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
@property
def supported_features(self):
"""Flag of features that are supported."""
return SUPPORT_UE_SMART_RADIO
@property
def media_content_type(self):
"""Return the media content type."""
return MEDIA_TYPE_MUSIC
@property
def media_image_url(self):
"""Image URL of current playing media."""
return self._media_artwork_url
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
return self._media_artist
@property
def media_title(self):
"""Title of current playing media."""
return self._media_title
def turn_on(self):
"""Turn on specified media player or all."""
self.send_command(["power", 1])
def turn_off(self):
"""Turn off specified media player or all."""
self.send_command(["power", 0])
def media_play(self):
"""Send the media player the command for play/pause."""
self.send_command(["play"])
def media_pause(self):
"""Send the media player the command for pause."""
self.send_command(["pause"])
def media_stop(self):
"""Send the media player the stop command."""
self.send_command(["stop"])
def media_previous_track(self):
"""Send the media player the command for prev track."""
self.send_command(["button", "rew"])
def media_next_track(self):
"""Send the media player the command for next track."""
self.send_command(["button", "fwd"])
def mute_volume(self, mute):
"""Send mute command."""
if mute:
self._last_volume = self._volume
self.send_command(["mixer", "volume", 0])
else:
self.send_command(["mixer", "volume", self._last_volume * 100])
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self.send_command(["mixer", "volume", volume * 100])

View file

@ -322,12 +322,15 @@ class LgWebOSDevice(MediaPlayerDevice):
def select_source(self, source):
"""Select input source."""
if self._source_list.get(source).get('title'):
self._current_source_id = self._source_list[source]['id']
source = self._source_list.get(source)
if source is None:
_LOGGER.warning("Source %s not found for %s", source, self.name)
return
self._current_source_id = self._source_list[source]['id']
if source.get('title'):
self._current_source = self._source_list[source]['title']
self._client.launch_app(self._source_list[source]['id'])
elif self._source_list.get(source).get('label'):
self._current_source_id = self._source_list[source]['id']
elif source.get('label'):
self._current_source = self._source_list[source]['label']
self._client.set_input(self._source_list[source]['id'])

View file

@ -36,7 +36,7 @@ SUPPORTED_FEATURES = (
KNOWN_HOSTS_KEY = 'data_yamaha_musiccast'
INTERVAL_SECONDS = 'interval_seconds'
REQUIREMENTS = ['pymusiccast==0.1.5']
REQUIREMENTS = ['pymusiccast==0.1.6']
DEFAULT_PORT = 5005
DEFAULT_INTERVAL = 480

View file

@ -0,0 +1,174 @@
"""
Support for interface with a Ziggo Mediabox XL.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.ziggo_mediabox_xl/
"""
import logging
import socket
import voluptuous as vol
from homeassistant.components.media_player import (
PLATFORM_SCHEMA, MediaPlayerDevice,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_PLAY, SUPPORT_PAUSE)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['ziggo-mediabox-xl==1.0.0']
_LOGGER = logging.getLogger(__name__)
DATA_KNOWN_DEVICES = 'ziggo_mediabox_xl_known_devices'
SUPPORT_ZIGGO = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
SUPPORT_NEXT_TRACK | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Ziggo Mediabox XL platform."""
from ziggo_mediabox_xl import ZiggoMediaboxXL
hass.data[DATA_KNOWN_DEVICES] = known_devices = set()
# Is this a manual configuration?
if config.get(CONF_HOST) is not None:
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
elif discovery_info is not None:
host = discovery_info.get('host')
name = discovery_info.get('name')
else:
_LOGGER.error("Cannot determine device")
return
# Only add a device once, so discovered devices do not override manual
# config.
hosts = []
ip_addr = socket.gethostbyname(host)
if ip_addr not in known_devices:
try:
mediabox = ZiggoMediaboxXL(ip_addr)
if mediabox.test_connection():
hosts.append(ZiggoMediaboxXLDevice(mediabox, host, name))
known_devices.add(ip_addr)
else:
_LOGGER.error("Can't connect to %s", host)
except socket.error as error:
_LOGGER.error("Can't connect to %s: %s", host, error)
else:
_LOGGER.info("Ignoring duplicate Ziggo Mediabox XL %s", host)
add_devices(hosts, True)
class ZiggoMediaboxXLDevice(MediaPlayerDevice):
"""Representation of a Ziggo Mediabox XL Device."""
def __init__(self, mediabox, host, name):
"""Initialize the device."""
# Generate a configuration for the Samsung library
self._mediabox = mediabox
self._host = host
self._name = name
self._state = None
def update(self):
"""Retrieve the state of the device."""
try:
if self._mediabox.turned_on():
if self._state != STATE_PAUSED:
self._state = STATE_PLAYING
else:
self._state = STATE_OFF
except socket.error:
_LOGGER.error("Couldn't fetch state from %s", self._host)
def send_keys(self, keys):
"""Send keys to the device and handle exceptions."""
try:
self._mediabox.send_keys(keys)
except socket.error:
_LOGGER.error("Couldn't send keys to %s", self._host)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def source_list(self):
"""List of available sources (channels)."""
return [self._mediabox.channels()[c]
for c in sorted(self._mediabox.channels().keys())]
@property
def supported_features(self):
"""Flag media player features that are supported."""
return SUPPORT_ZIGGO
def turn_on(self):
"""Turn the media player on."""
self.send_keys(['POWER'])
self._state = STATE_ON
def turn_off(self):
"""Turn off media player."""
self.send_keys(['POWER'])
self._state = STATE_OFF
def media_play(self):
"""Send play command."""
self.send_keys(['PLAY'])
self._state = STATE_PLAYING
def media_pause(self):
"""Send pause command."""
self.send_keys(['PAUSE'])
self._state = STATE_PAUSED
def media_play_pause(self):
"""Simulate play pause media player."""
self.send_keys(['PAUSE'])
if self._state == STATE_PAUSED:
self._state = STATE_PLAYING
else:
self._state = STATE_PAUSED
def media_next_track(self):
"""Channel up."""
self.send_keys(['CHAN_UP'])
self._state = STATE_PLAYING
def media_previous_track(self):
"""Channel down."""
self.send_keys(['CHAN_DOWN'])
self._state = STATE_PLAYING
def select_source(self, source):
"""Select the channel."""
if str(source).isdigit():
digits = str(source)
else:
digits = next((
key for key, value in self._mediabox.channels().items()
if value == source), None)
if digits is None:
return
self.send_keys(['NUM_{}'.format(digit)
for digit in str(digits)])
self._state = STATE_PLAYING

View file

@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mochad/
"""
import logging
import threading
import voluptuous as vol
@ -23,6 +24,8 @@ CONF_COMM_TYPE = 'comm_type'
DOMAIN = 'mochad'
REQ_LOCK = threading.Lock()
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default='localhost'): cv.string,

View file

@ -17,8 +17,8 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.3.zip'
'#pybotvac==0.0.3']
REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.4.zip'
'#pybotvac==0.0.4']
DOMAIN = 'neato'
NEATO_ROBOTS = 'neato_robots'

View file

@ -14,12 +14,13 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components import recorder
from homeassistant.const import (
CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE, TEMP_CELSIUS,
EVENT_STATE_CHANGED, TEMP_FAHRENHEIT, CONTENT_TYPE_TEXT_PLAIN)
EVENT_STATE_CHANGED, TEMP_FAHRENHEIT, CONTENT_TYPE_TEXT_PLAIN,
ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT)
from homeassistant import core as hacore
from homeassistant.helpers import state as state_helper
from homeassistant.util.temperature import fahrenheit_to_celsius
REQUIREMENTS = ['prometheus_client==0.0.19']
REQUIREMENTS = ['prometheus_client==0.0.21']
_LOGGER = logging.getLogger(__name__)
@ -159,6 +160,26 @@ class Metrics(object):
value = state_helper.state_as_number(state)
metric.labels(**self._labels(state)).set(value)
def _handle_climate(self, state):
temp = state.attributes.get(ATTR_TEMPERATURE)
if temp:
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if unit == TEMP_FAHRENHEIT:
temp = fahrenheit_to_celsius(temp)
metric = self._metric(
'temperature_c', self.prometheus_client.Gauge,
'Temperature in degrees Celsius')
metric.labels(**self._labels(state)).set(temp)
metric = self._metric(
'climate_state', self.prometheus_client.Gauge,
'State of the thermostat (0/1)')
try:
value = state_helper.state_as_number(state)
metric.labels(**self._labels(state)).set(value)
except ValueError:
pass
def _handle_sensor(self, state):
_sensor_types = {
TEMP_CELSIUS: (
@ -189,9 +210,17 @@ class Metrics(object):
'electricity_usage_w', self.prometheus_client.Gauge,
'Currently reported electricity draw in Watts',
),
'min': (
'sensor_min', self.prometheus_client.Gauge,
'Time in minutes reported by a sensor'
),
'Events': (
'sensor_event_count', self.prometheus_client.Gauge,
'Number of events for a sensor'
),
}
unit = state.attributes.get('unit_of_measurement')
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
metric = _sensor_types.get(unit)
if metric is not None:
@ -212,12 +241,25 @@ class Metrics(object):
self.prometheus_client.Gauge,
'State of the switch (0/1)',
)
value = state_helper.state_as_number(state)
metric.labels(**self._labels(state)).set(value)
try:
value = state_helper.state_as_number(state)
metric.labels(**self._labels(state)).set(value)
except ValueError:
pass
def _handle_zwave(self, state):
self._battery(state)
def _handle_automation(self, state):
metric = self._metric(
'automation_triggered_count',
self.prometheus_client.Counter,
'Count of times an automation has been triggered',
)
metric.labels(**self._labels(state)).inc()
class PrometheusView(HomeAssistantView):
"""Handle Prometheus requests."""

View file

@ -0,0 +1,60 @@
"""
Support for Vera scenes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.vera/
"""
import logging
from homeassistant.util import slugify
from homeassistant.components.scene import Scene
from homeassistant.components.vera import (
VERA_CONTROLLER, VERA_SCENES, VERA_ID_FORMAT)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera scenes."""
add_devices(
[VeraScene(scene, hass.data[VERA_CONTROLLER])
for scene in hass.data[VERA_SCENES]], True)
class VeraScene(Scene):
"""Representation of a Vera scene entity."""
def __init__(self, vera_scene, controller):
"""Initialize the scene."""
self.vera_scene = vera_scene
self.controller = controller
self._name = self.vera_scene.name
# Append device id to prevent name clashes in HA.
self.vera_id = VERA_ID_FORMAT.format(
slugify(vera_scene.name), vera_scene.scene_id)
def update(self):
"""Update the scene status."""
self.vera_scene.refresh()
def activate(self, **kwargs):
"""Activate the scene."""
self.vera_scene.activate()
@property
def name(self):
"""Return the name of the scene."""
return self._name
@property
def device_state_attributes(self):
"""Return the state attributes of the scene."""
return {'vera_scene_id': self.vera_scene.vera_scene_id}
@property
def should_poll(self):
"""Return that polling is not necessary."""
return False

View file

@ -0,0 +1,103 @@
"""
Support for ADS sensors.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/sensor.ads/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components import ads
from homeassistant.components.ads import CONF_ADS_VAR, CONF_ADS_TYPE, \
CONF_ADS_FACTOR
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'ADS sensor'
DEPENDENCIES = ['ads']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=''): cv.string,
vol.Optional(CONF_ADS_TYPE, default=ads.ADSTYPE_INT): vol.In(
[ads.ADSTYPE_INT, ads.ADSTYPE_UINT, ads.ADSTYPE_BYTE]
),
vol.Optional(CONF_ADS_FACTOR): cv.positive_int,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an ADS sensor device."""
ads_hub = hass.data.get(ads.DATA_ADS)
ads_var = config.get(CONF_ADS_VAR)
ads_type = config.get(CONF_ADS_TYPE)
name = config.get(CONF_NAME)
unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
factor = config.get(CONF_ADS_FACTOR)
entity = AdsSensor(ads_hub, ads_var, ads_type, name,
unit_of_measurement, factor)
add_devices([entity])
class AdsSensor(Entity):
"""Representation of an ADS sensor entity."""
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement,
factor):
"""Initialize AdsSensor entity."""
self._ads_hub = ads_hub
self._name = name
self._value = None
self._unit_of_measurement = unit_of_measurement
self.ads_var = ads_var
self.ads_type = ads_type
self.factor = factor
@asyncio.coroutine
def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notifications."""
_LOGGER.debug('Variable %s changed its value to %d', name, value)
# if factor is set use it otherwise not
if self.factor is None:
self._value = value
else:
self._value = value / self.factor
self.schedule_update_ha_state()
self.hass.async_add_job(
self._ads_hub.add_device_notification,
self.ads_var, self._ads_hub.ADS_TYPEMAP[self.ads_type], update
)
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._value
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def should_poll(self):
"""Return False because entity pushes its state to HA."""
return False

View file

@ -7,25 +7,21 @@ https://home-assistant.io/components/sensor.alarmdecoder/
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.components.alarmdecoder import (SIGNAL_PANEL_MESSAGE)
from homeassistant.const import (STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['alarmdecoder']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up for AlarmDecoder sensor devices."""
_LOGGER.debug("AlarmDecoderSensor: async_setup_platform")
_LOGGER.debug("AlarmDecoderSensor: setup_platform")
device = AlarmDecoderSensor(hass)
async_add_devices([device])
add_devices([device])
class AlarmDecoderSensor(Entity):
@ -34,23 +30,20 @@ class AlarmDecoderSensor(Entity):
def __init__(self, hass):
"""Initialize the alarm panel."""
self._display = ""
self._state = STATE_UNKNOWN
self._state = None
self._icon = 'mdi:alarm-check'
self._name = 'Alarm Panel Display'
_LOGGER.debug("Setting up panel")
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_PANEL_MESSAGE, self._message_callback)
@callback
def _message_callback(self, message):
if self._display != message.text:
self._display = message.text
self.async_schedule_update_ha_state()
self.schedule_update_ha_state()
@property
def icon(self):

View file

@ -0,0 +1,110 @@
"""
Stock market information from Alpha Vantage.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.alpha_vantage/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['alpha_vantage==1.3.6']
_LOGGER = logging.getLogger(__name__)
ATTR_CLOSE = 'close'
ATTR_HIGH = 'high'
ATTR_LOW = 'low'
ATTR_VOLUME = 'volume'
CONF_ATTRIBUTION = "Stock market information provided by Alpha Vantage."
CONF_SYMBOLS = 'symbols'
DEFAULT_SYMBOL = 'GOOGL'
ICON = 'mdi:currency-usd'
SCAN_INTERVAL = timedelta(minutes=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_SYMBOLS, default=[DEFAULT_SYMBOL]):
vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Alpha Vantage sensor."""
from alpha_vantage.timeseries import TimeSeries
api_key = config.get(CONF_API_KEY)
symbols = config.get(CONF_SYMBOLS)
timeseries = TimeSeries(key=api_key)
dev = []
for symbol in symbols:
try:
timeseries.get_intraday(symbol)
except ValueError:
_LOGGER.error(
"API Key is not valid or symbol '%s' not known", symbol)
return
dev.append(AlphaVantageSensor(timeseries, symbol))
add_devices(dev, True)
class AlphaVantageSensor(Entity):
"""Representation of a Alpha Vantage sensor."""
def __init__(self, timeseries, symbol):
"""Initialize the sensor."""
self._name = symbol
self._timeseries = timeseries
self._symbol = symbol
self.values = None
self._unit_of_measurement = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._symbol
@property
def state(self):
"""Return the state of the sensor."""
return self.values['1. open']
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.values is not None:
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_CLOSE: self.values['4. close'],
ATTR_HIGH: self.values['2. high'],
ATTR_LOW: self.values['3. low'],
ATTR_VOLUME: self.values['5. volume'],
}
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return ICON
def update(self):
"""Get the latest data and updates the states."""
all_values, _ = self._timeseries.get_intraday(self._symbol)
self.values = next(iter(all_values.values()))

View file

@ -0,0 +1,85 @@
"""
Support for Canary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.canary/
"""
from homeassistant.components.canary import DATA_CANARY
from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
DEPENDENCIES = ['canary']
SENSOR_VALUE_PRECISION = 1
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Canary sensors."""
data = hass.data[DATA_CANARY]
devices = []
from canary.api import SensorType
for location in data.locations:
for device in location.devices:
if device.is_online:
for sensor_type in SensorType:
devices.append(CanarySensor(data, sensor_type, location,
device))
add_devices(devices, True)
class CanarySensor(Entity):
"""Representation of a Canary sensor."""
def __init__(self, data, sensor_type, location, device):
"""Initialize the sensor."""
self._data = data
self._sensor_type = sensor_type
self._device_id = device.device_id
self._is_celsius = location.is_celsius
self._sensor_value = None
sensor_type_name = sensor_type.value.replace("_", " ").title()
self._name = '{} {} {}'.format(location.name,
device.name,
sensor_type_name)
@property
def name(self):
"""Return the name of the Canary sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._sensor_value
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "sensor_canary_{}_{}".format(self._device_id,
self._sensor_type.value)
@property
def unit_of_measurement(self):
"""Return the unit of measurement this sensor expresses itself in."""
from canary.api import SensorType
if self._sensor_type == SensorType.TEMPERATURE:
return TEMP_CELSIUS if self._is_celsius else TEMP_FAHRENHEIT
elif self._sensor_type == SensorType.HUMIDITY:
return "%"
elif self._sensor_type == SensorType.AIR_QUALITY:
return ""
return None
def update(self):
"""Get the latest state of the sensor."""
self._data.update()
readings = self._data.get_readings(self._device_id)
value = next((
reading.value for reading in readings
if reading.sensor_type == self._sensor_type), None)
if value is not None:
self._sensor_value = round(float(value), SENSOR_VALUE_PRECISION)

View file

@ -31,6 +31,7 @@ CONF_COST = 'cost'
CONF_CURRENT_VALUES = 'current_values'
DEFAULT_PERIOD = 'year'
DEFAULT_UTC_OFFSET = '0'
SENSOR_TYPES = {
CONF_INSTANT: ['Energy Usage', 'W'],
@ -50,7 +51,7 @@ SENSORS_SCHEMA = vol.Schema({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_APPTOKEN): cv.string,
vol.Optional(CONF_UTC_OFFSET): cv.string,
vol.Optional(CONF_UTC_OFFSET, default=DEFAULT_UTC_OFFSET): cv.string,
vol.Required(CONF_MONITORED_VARIABLES): [SENSORS_SCHEMA]
})

View file

@ -30,7 +30,7 @@ UNIT_OF_MEASUREMENT = 'W'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_CHANNEL_ID): cv.positive_int,
vol.Required(CONF_CHANNEL_ID): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

View file

@ -0,0 +1,127 @@
"""
Parse prices of a item from gearbest.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.gearbest/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_URL, CONF_CURRENCY)
REQUIREMENTS = ['gearbest_parser==1.0.5']
_LOGGER = logging.getLogger(__name__)
CONF_ITEMS = 'items'
ICON = 'mdi:coin'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2*60*60) # 2h
MIN_TIME_BETWEEN_CURRENCY_UPDATES = timedelta(seconds=12*60*60) # 12h
_ITEM_SCHEMA = vol.All(
vol.Schema({
vol.Exclusive(CONF_URL, 'XOR'): cv.string,
vol.Exclusive(CONF_ID, 'XOR'): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_CURRENCY): cv.string
}), cv.has_at_least_one_key(CONF_URL, CONF_ID)
)
_ITEMS_SCHEMA = vol.Schema([_ITEM_SCHEMA])
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ITEMS): _ITEMS_SCHEMA,
vol.Required(CONF_CURRENCY): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Gearbest sensor."""
from gearbest_parser import CurrencyConverter
currency = config.get(CONF_CURRENCY)
sensors = []
items = config.get(CONF_ITEMS)
converter = CurrencyConverter()
converter.update()
for item in items:
try:
sensors.append(GearbestSensor(converter, item, currency))
except ValueError as exc:
_LOGGER.error(exc)
def currency_update(event_time):
"""Update currency list."""
converter.update()
track_time_interval(hass,
currency_update,
MIN_TIME_BETWEEN_CURRENCY_UPDATES)
add_devices(sensors, True)
class GearbestSensor(Entity):
"""Implementation of the sensor."""
def __init__(self, converter, item, currency):
"""Initialize the sensor."""
from gearbest_parser import GearbestParser
self._name = item.get(CONF_NAME)
self._parser = GearbestParser()
self._parser.set_currency_converter(converter)
self._item = self._parser.load(item.get(CONF_ID),
item.get(CONF_URL),
item.get(CONF_CURRENCY, currency))
if self._item is None:
raise ValueError("id and url could not be resolved")
@property
def name(self):
"""Return the name of the item."""
return self._name if self._name is not None else self._item.name
@property
def icon(self):
"""Return the icon for the frontend."""
return ICON
@property
def state(self):
"""Return the price of the selected product."""
return self._item.price
@property
def unit_of_measurement(self):
"""Return the currency."""
return self._item.currency
@property
def entity_picture(self):
"""Return the image."""
return self._item.image
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {'name': self._item.name,
'description': self._item.description,
'currency': self._item.currency,
'url': self._item.url}
return attrs
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest price from gearbest and updates the state."""
self._item.update()

View file

@ -282,6 +282,9 @@ class ISYSensorDevice(isy.ISYDevice):
@property
def state(self) -> str:
"""Get the state of the ISY994 sensor device."""
if self.is_unknown():
return None
if len(self._node.uom) == 1:
if self._node.uom[0] in UOM_TO_STATES:
states = UOM_TO_STATES.get(self._node.uom[0])

View file

@ -5,85 +5,94 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.luftdaten/
"""
import asyncio
import json
import logging
from datetime import timedelta
import logging
import requests
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME, CONF_RESOURCE, CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS,
TEMP_CELSIUS)
from homeassistant.helpers.entity import Entity
ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
REQUIREMENTS = ['luftdaten==0.1.1']
_LOGGER = logging.getLogger(__name__)
ATTR_SENSOR_ID = 'sensor_id'
CONF_ATTRIBUTION = "Data provided by luftdaten.info"
VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3'
SENSOR_TEMPERATURE = 'temperature'
SENSOR_HUMIDITY = 'humidity'
SENSOR_PM10 = 'P1'
SENSOR_PM2_5 = 'P2'
SENSOR_PRESSURE = 'pressure'
SENSOR_TYPES = {
SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS],
SENSOR_HUMIDITY: ['Humidity', '%'],
SENSOR_PRESSURE: ['Pressure', 'Pa'],
SENSOR_PM10: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER],
SENSOR_PM2_5: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER]
}
DEFAULT_NAME = 'Luftdaten Sensor'
DEFAULT_RESOURCE = 'https://api.luftdaten.info/v1/sensor/'
DEFAULT_VERIFY_SSL = True
DEFAULT_NAME = 'Luftdaten'
CONF_SENSORID = 'sensorid'
SCAN_INTERVAL = timedelta(minutes=3)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORID): cv.positive_int,
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_RESOURCE, default=DEFAULT_RESOURCE): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Luftdaten sensor."""
from luftdaten import Luftdaten
name = config.get(CONF_NAME)
sensorid = config.get(CONF_SENSORID)
verify_ssl = config.get(CONF_VERIFY_SSL)
sensor_id = config.get(CONF_SENSORID)
resource = '{}{}/'.format(config.get(CONF_RESOURCE), sensorid)
session = async_get_clientsession(hass)
luftdaten = LuftdatenData(Luftdaten(sensor_id, hass.loop, session))
rest_client = LuftdatenData(resource, verify_ssl)
rest_client.update()
yield from luftdaten.async_update()
if rest_client.data is None:
_LOGGER.error("Unable to fetch Luftdaten data")
return False
if luftdaten.data is None:
_LOGGER.error("Sensor is not available: %s", sensor_id)
return
devices = []
for variable in config[CONF_MONITORED_CONDITIONS]:
devices.append(LuftdatenSensor(rest_client, name, variable))
if luftdaten.data.values[variable] is None:
_LOGGER.warning("It might be that sensor %s is not providing "
"measurements for %s", sensor_id, variable)
devices.append(LuftdatenSensor(luftdaten, name, variable, sensor_id))
async_add_devices(devices, True)
async_add_devices(devices)
class LuftdatenSensor(Entity):
"""Implementation of a LuftdatenSensor sensor."""
"""Implementation of a Luftdaten sensor."""
def __init__(self, rest_client, name, sensor_type):
"""Initialize the LuftdatenSensor sensor."""
self.rest_client = rest_client
def __init__(self, luftdaten, name, sensor_type, sensor_id):
"""Initialize the Luftdaten sensor."""
self.luftdaten = luftdaten
self._name = name
self._state = None
self._sensor_id = sensor_id
self.sensor_type = sensor_type
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
@ -95,48 +104,50 @@ class LuftdatenSensor(Entity):
@property
def state(self):
"""Return the state of the device."""
return self._state
return self.luftdaten.data.values[self.sensor_type]
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
def update(self):
"""Get the latest data from REST API and update the state."""
self.rest_client.update()
value = self.rest_client.data
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.luftdaten.data.meta is None:
return
if value is None:
self._state = None
else:
parsed_json = json.loads(value)
attr = {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_SENSOR_ID: self._sensor_id,
'lat': self.luftdaten.data.meta['latitude'],
'long': self.luftdaten.data.meta['longitude'],
}
return attr
log_entries_count = len(parsed_json) - 1
latest_log_entry = parsed_json[log_entries_count]
sensordata_values = latest_log_entry['sensordatavalues']
for sensordata_value in sensordata_values:
if sensordata_value['value_type'] == self.sensor_type:
self._state = sensordata_value['value']
@asyncio.coroutine
def async_update(self):
"""Get the latest data from luftdaten.info and update the state."""
try:
yield from self.luftdaten.async_update()
except TypeError:
pass
class LuftdatenData(object):
"""Class for handling the data retrieval."""
def __init__(self, resource, verify_ssl):
def __init__(self, data):
"""Initialize the data object."""
self._request = requests.Request('GET', resource).prepare()
self._verify_ssl = verify_ssl
self.data = None
self.data = data
@Throttle(MIN_TIME_BETWEEN_UPDATES)
@asyncio.coroutine
def async_update(self):
"""Get the latest data from luftdaten.info."""
from luftdaten.exceptions import LuftdatenError
def update(self):
"""Get the latest data from Luftdaten service."""
try:
with requests.Session() as sess:
response = sess.send(
self._request, timeout=10, verify=self._verify_ssl)
self.data = response.text
except requests.exceptions.RequestException:
_LOGGER.error("Error fetching data: %s", self._request)
self.data = None
yield from self.data.async_get_data()
except LuftdatenError:
_LOGGER.error("Unable to retrieve data from luftdaten.info")

View file

@ -12,7 +12,8 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_MAC)
CONF_FORCE_UPDATE, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_MAC
)
REQUIREMENTS = ['miflora==0.1.16']
@ -20,7 +21,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_ADAPTER = 'adapter'
CONF_CACHE = 'cache_value'
CONF_FORCE_UPDATE = 'force_update'
CONF_MEDIAN = 'median'
CONF_RETRIES = 'retries'
CONF_TIMEOUT = 'timeout'

View file

@ -13,7 +13,8 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT)
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN,
CONF_UNIT_OF_MEASUREMENT)
from homeassistant.helpers.entity import Entity
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv
@ -22,7 +23,6 @@ from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
CONF_FORCE_UPDATE = 'force_update'
CONF_EXPIRE_AFTER = 'expire_after'
DEFAULT_NAME = 'MQTT Sensor'

View file

@ -45,7 +45,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name = config.get(CONF_NAME)
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)
tools = octoprint_api.get_tools()
_LOGGER.error(str(tools))
if "Temperatures" in monitored_conditions:
if not tools:

View file

@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rest/
"""
import logging
import json
import voluptuous as vol
import requests
@ -12,10 +13,11 @@ from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, CONF_VERIFY_SSL, CONF_USERNAME,
CONF_PASSWORD, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION, CONF_HEADERS)
CONF_AUTHENTICATION, CONF_FORCE_UPDATE, CONF_HEADERS, CONF_NAME,
CONF_METHOD, CONF_PASSWORD, CONF_PAYLOAD, CONF_RESOURCE,
CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME,
CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
@ -24,7 +26,9 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_METHOD = 'GET'
DEFAULT_NAME = 'REST Sensor'
DEFAULT_VERIFY_SSL = True
DEFAULT_FORCE_UPDATE = False
CONF_JSON_ATTRS = 'json_attributes'
METHODS = ['POST', 'GET']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -32,6 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): {cv.string: cv.string},
vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv,
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
@ -40,6 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
})
@ -55,6 +61,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
headers = config.get(CONF_HEADERS)
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
value_template = config.get(CONF_VALUE_TEMPLATE)
json_attrs = config.get(CONF_JSON_ATTRS)
force_update = config.get(CONF_FORCE_UPDATE)
if value_template is not None:
value_template.hass = hass
@ -68,13 +77,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
add_devices([RestSensor(hass, rest, name, unit, value_template)], True)
add_devices([RestSensor(
hass, rest, name, unit, value_template, json_attrs, force_update
)], True)
class RestSensor(Entity):
"""Implementation of a REST sensor."""
def __init__(self, hass, rest, name, unit_of_measurement, value_template):
def __init__(self, hass, rest, name, unit_of_measurement,
value_template, json_attrs, force_update):
"""Initialize the REST sensor."""
self._hass = hass
self.rest = rest
@ -82,6 +94,9 @@ class RestSensor(Entity):
self._state = STATE_UNKNOWN
self._unit_of_measurement = unit_of_measurement
self._value_template = value_template
self._json_attrs = json_attrs
self._attributes = None
self._force_update = force_update
@property
def name(self):
@ -103,11 +118,30 @@ class RestSensor(Entity):
"""Return the state of the device."""
return self._state
@property
def force_update(self):
"""Force update."""
return self._force_update
def update(self):
"""Get the latest data from REST API and update the state."""
self.rest.update()
value = self.rest.data
if self._json_attrs:
self._attributes = {}
try:
json_dict = json.loads(value)
if isinstance(json_dict, dict):
attrs = {k: json_dict[k] for k in self._json_attrs
if k in json_dict}
self._attributes = attrs
else:
_LOGGER.warning("JSON result was not a dictionary")
except ValueError:
_LOGGER.warning("REST result could not be parsed as JSON")
_LOGGER.debug("Erroneous JSON: %s", value)
if value is None:
value = STATE_UNKNOWN
elif self._value_template is not None:
@ -116,6 +150,11 @@ class RestSensor(Entity):
self._state = value
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
class RestData(object):
"""Class for handling the data retrieval."""

View file

@ -13,7 +13,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['python-ripple-api==0.0.2']
REQUIREMENTS = ['python-ripple-api==0.0.3']
CONF_ADDRESS = 'address'
CONF_ATTRIBUTION = "Data provided by ripple.com"
@ -71,4 +71,6 @@ class RippleSensor(Entity):
def update(self):
"""Get the latest state of the sensor."""
from pyripple import get_balance
self._state = get_balance(self.address)
balance = get_balance(self.address)
if balance is not None:
self._state = balance

View file

@ -14,7 +14,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['shodan==1.7.5']
REQUIREMENTS = ['shodan==1.7.7']
_LOGGER = logging.getLogger(__name__)

View file

@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['psutil==5.4.1']
REQUIREMENTS = ['psutil==5.4.2']
_LOGGER = logging.getLogger(__name__)

View file

@ -39,7 +39,7 @@ class TeslaSensor(TeslaDevice, Entity):
def __init__(self, tesla_device, controller, sensor_type=None):
"""Initialisation of the sensor."""
self.current_value = None
self._temperature_units = None
self._unit = None
self.last_changed_time = None
self.type = sensor_type
super().__init__(tesla_device, controller)
@ -59,7 +59,7 @@ class TeslaSensor(TeslaDevice, Entity):
@property
def unit_of_measurement(self):
"""Return the unit_of_measurement of the device."""
return self._temperature_units
return self._unit
def update(self):
"""Update the state from the sensor."""
@ -74,8 +74,9 @@ class TeslaSensor(TeslaDevice, Entity):
tesla_temp_units = self.tesla_device.measurement
if tesla_temp_units == 'F':
self._temperature_units = TEMP_FAHRENHEIT
self._unit = TEMP_FAHRENHEIT
else:
self._temperature_units = TEMP_CELSIUS
self._unit = TEMP_CELSIUS
else:
self.current_value = self.tesla_device.battery_level()
self._unit = "%"

View file

@ -25,8 +25,8 @@ SCAN_INTERVAL = timedelta(seconds=5)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera controller devices."""
add_devices(
VeraSensor(device, VERA_CONTROLLER)
for device in VERA_DEVICES['sensor'])
VeraSensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['sensor'])
class VeraSensor(VeraDevice, Entity):

View file

@ -6,8 +6,10 @@ https://home-assistant.io/components/sensor.volvooncall/
"""
import logging
from math import floor
from homeassistant.components.volvooncall import VolvoEntity, RESOURCES
from homeassistant.components.volvooncall import (
VolvoEntity, RESOURCES, CONF_SCANDINAVIAN_MILES)
_LOGGER = logging.getLogger(__name__)
@ -26,14 +28,37 @@ class VolvoSensor(VolvoEntity):
def state(self):
"""Return the state of the sensor."""
val = getattr(self.vehicle, self._attribute)
if val is None:
return val
if self._attribute == 'odometer':
return round(val / 1000) # km
return val
val /= 1000 # m -> km
if 'mil' in self.unit_of_measurement:
val /= 10 # km -> mil
if self._attribute == 'average_fuel_consumption':
val /= 10 # L/1000km -> L/100km
if 'mil' in self.unit_of_measurement:
return round(val, 2)
else:
return round(val, 1)
elif self._attribute == 'distance_to_empty':
return int(floor(val))
else:
return int(round(val))
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return RESOURCES[self._attribute][3]
unit = RESOURCES[self._attribute][3]
if self._state.config[CONF_SCANDINAVIAN_MILES] and 'km' in unit:
if self._attribute == 'average_fuel_consumption':
return 'L/mil'
else:
return unit.replace('km', 'mil')
return unit
@property
def icon(self):

View file

@ -32,55 +32,6 @@ foursquare:
description: Vertical accuracy of the user's location, in meters.
example: 1
homematic:
virtualkey:
description: Press a virtual key from CCU/Homegear or simulate keypress.
fields:
address:
description: Address of homematic device or BidCoS-RF for virtual remote.
example: BidCoS-RF
channel:
description: Channel for calling a keypress.
example: 1
param:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT.
example: PRESS_LONG
proxy:
description: (Optional) for set a hosts value.
example: Hosts name from config
set_var_value:
description: Set the name of a node.
fields:
entity_id:
description: Name(s) of homematic central to set value.
example: 'homematic.ccu2'
name:
description: Name of the variable to set.
example: 'testvariable'
value:
description: New value
example: 1
set_dev_value:
description: Set a device property on RPC XML interface.
fields:
address:
description: Address of homematic device or BidCoS-RF for virtual remote
example: BidCoS-RF
channel:
description: Channel for calling a keypress
example: 1
param:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT
example: PRESS_LONG
proxy:
description: (Optional) for set a hosts value
example: Hosts name from config
value:
description: New value
example: 1
reconnect:
description: Reconnect to all Homematic Hubs.
microsoft_face:
create_group:
description: Create a new person group.
@ -437,7 +388,7 @@ input_text:
set_value:
description: Set the value of an input text entity.
fields:
entity_id:
entity_id:
description: Entity id of the input text to set the new value.
example: 'input_text.text1'
value:
@ -448,7 +399,7 @@ input_number:
set_value:
description: Set the value of an input number entity.
fields:
entity_id:
entity_id:
description: Entity id of the input number to set the new value.
example: 'input_number.threshold'
value:
@ -457,13 +408,13 @@ input_number:
increment:
description: Increment the value of an input number entity by its stepping.
fields:
entity_id:
entity_id:
description: Entity id of the input number the should be incremented.
example: 'input_number.threshold'
decrement:
description: Decrement the value of an input number entity by its stepping.
fields:
entity_id:
entity_id:
description: Entity id of the input number the should be decremented.
example: 'input_number.threshold'

View file

@ -15,7 +15,7 @@ DEPENDENCIES = ['mqtt']
CONF_INTENTS = 'intents'
CONF_ACTION = 'action'
INTENT_TOPIC = 'hermes/nlu/intentParsed'
INTENT_TOPIC = 'hermes/intent/#'
_LOGGER = logging.getLogger(__name__)
@ -32,7 +32,8 @@ INTENT_SCHEMA = vol.Schema({
vol.Required('slotName'): str,
vol.Required('value'): {
vol.Required('kind'): str,
vol.Required('value'): cv.match_all
vol.Optional('value'): cv.match_all,
vol.Optional('rawValue'): cv.match_all
}
}]
}, extra=vol.ALLOW_EXTRA)
@ -59,8 +60,12 @@ def async_setup(hass, config):
return
intent_type = request['intent']['intentName'].split('__')[-1]
slots = {slot['slotName']: {'value': slot['value']['value']}
for slot in request.get('slots', [])}
slots = {}
for slot in request.get('slots', []):
if 'value' in slot['value']:
slots[slot['slotName']] = {'value': slot['value']['value']}
else:
slots[slot['slotName']] = {'value': slot['rawValue']}
try:
yield from intent.async_handle(

View file

@ -0,0 +1,85 @@
"""
Support for ADS switch platform.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/switch.ads/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS Switch'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up switch platform for ADS."""
ads_hub = hass.data.get(DATA_ADS)
name = config.get(CONF_NAME)
ads_var = config.get(CONF_ADS_VAR)
add_devices([AdsSwitch(ads_hub, name, ads_var)], True)
class AdsSwitch(ToggleEntity):
"""Representation of an Ads switch device."""
def __init__(self, ads_hub, name, ads_var):
"""Initialize the AdsSwitch entity."""
self._ads_hub = ads_hub
self._on_state = False
self._name = name
self.ads_var = ads_var
@asyncio.coroutine
def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notification."""
_LOGGER.debug('Variable %s changed its value to %d',
name, value)
self._on_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
)
@property
def is_on(self):
"""Return if the switch is turned on."""
return self._on_state
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def should_poll(self):
"""Return False because entity pushes its state to HA."""
return False
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._ads_hub.write_by_name(self.ads_var, True,
self._ads_hub.PLCTYPE_BOOL)
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._ads_hub.write_by_name(self.ads_var, False,
self._ads_hub.PLCTYPE_BOOL)

View file

@ -69,7 +69,10 @@ class ISYSwitchDevice(isy.ISYDevice, SwitchDevice):
@property
def state(self) -> str:
"""Get the state of the ISY994 device."""
return VALUE_TO_STATE.get(bool(self.value), STATE_UNKNOWN)
if self.is_unknown():
return None
else:
return VALUE_TO_STATE.get(bool(self.value), STATE_UNKNOWN)
def turn_off(self, **kwargs) -> None:
"""Send the turn on command to the ISY994 switch."""

View file

@ -60,18 +60,21 @@ class MochadSwitch(SwitchDevice):
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._state = True
self.device.send_cmd('on')
self._controller.read_data()
with mochad.REQ_LOCK:
self.device.send_cmd('on')
self._controller.read_data()
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._state = False
self.device.send_cmd('off')
self._controller.read_data()
with mochad.REQ_LOCK:
self.device.send_cmd('off')
self._controller.read_data()
def _get_device_status(self):
"""Get the status of the switch from mochad."""
status = self.device.get_status().rstrip()
with mochad.REQ_LOCK:
status = self.device.get_status().rstrip()
return status == 'on'
@property

View file

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.tplink/
"""
import logging
import time
import voluptuous as vol
@ -23,9 +22,12 @@ ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_DAILY_CONSUMPTION = 'daily_consumption'
ATTR_CURRENT = 'current'
CONF_LEDS = 'enable_leds'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_LEDS, default=True): cv.boolean,
})
@ -35,18 +37,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
from pyHS100 import SmartPlug
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
leds_on = config.get(CONF_LEDS)
add_devices([SmartPlugSwitch(SmartPlug(host), name)], True)
add_devices([SmartPlugSwitch(SmartPlug(host), name, leds_on)], True)
class SmartPlugSwitch(SwitchDevice):
"""Representation of a TPLink Smart Plug switch."""
def __init__(self, smartplug, name):
def __init__(self, smartplug, name, leds_on):
"""Initialize the switch."""
self.smartplug = smartplug
self._name = name
self._leds_on = leds_on
self._state = None
self._available = True
# Set up emeter cache
self._emeter_params = {}
@ -55,6 +60,11 @@ class SmartPlugSwitch(SwitchDevice):
"""Return the name of the Smart Plug, if any."""
return self._name
@property
def available(self) -> bool:
"""Return if switch is available."""
return self._available
@property
def is_on(self):
"""Return true if switch is on."""
@ -77,12 +87,15 @@ class SmartPlugSwitch(SwitchDevice):
"""Update the TP-Link switch's state."""
from pyHS100 import SmartDeviceException
try:
self._available = True
self._state = self.smartplug.state == \
self.smartplug.SWITCH_STATE_ON
if self._name is None:
self._name = self.smartplug.alias
self.smartplug.led = self._leds_on
if self.smartplug.has_emeter:
emeter_readings = self.smartplug.get_emeter_realtime()
@ -100,8 +113,9 @@ class SmartPlugSwitch(SwitchDevice):
self._emeter_params[ATTR_DAILY_CONSUMPTION] \
= "%.2f kW" % emeter_statics[int(time.strftime("%e"))]
except KeyError:
# device returned no daily history
# Device returned no daily history
pass
except (SmartDeviceException, OSError) as ex:
_LOGGER.warning('Could not read state for %s: %s', self.name, ex)
_LOGGER.warning("Could not read state for %s: %s", self.name, ex)
self._available = False

View file

@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera switches."""
add_devices(
VeraSwitch(device, VERA_CONTROLLER) for
device in VERA_DEVICES['switch'])
VeraSwitch(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['switch'])
class VeraSwitch(VeraDevice, SwitchDevice):

View file

@ -17,7 +17,7 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['tellcore-py==1.1.2', 'tellcore-net==0.3']
REQUIREMENTS = ['tellcore-py==1.1.2', 'tellcore-net==0.4']
_LOGGER = logging.getLogger(__name__)
@ -67,7 +67,7 @@ def _discover(hass, config, component_name, found_tellcore_devices):
def setup(hass, config):
"""Set up the Tellstick component."""
from tellcore.constants import TELLSTICK_DIM
from tellcore.constants import (TELLSTICK_DIM, TELLSTICK_UP)
from tellcore.telldus import AsyncioCallbackDispatcher
from tellcore.telldus import TelldusCore
from tellcorenet import TellCoreClient
@ -102,16 +102,22 @@ def setup(hass, config):
hass.data[DATA_TELLSTICK] = {device.id: device for
device in tellcore_devices}
# Discover the switches
_discover(hass, config, 'switch',
[device.id for device in tellcore_devices
if not device.methods(TELLSTICK_DIM)])
# Discover the lights
_discover(hass, config, 'light',
[device.id for device in tellcore_devices
if device.methods(TELLSTICK_DIM)])
# Discover the cover
_discover(hass, config, 'cover',
[device.id for device in tellcore_devices
if device.methods(TELLSTICK_UP)])
# Discover the switches
_discover(hass, config, 'switch',
[device.id for device in tellcore_devices
if (not device.methods(TELLSTICK_UP) and
not device.methods(TELLSTICK_DIM))])
@callback
def async_handle_callback(tellcore_id, tellcore_command,
tellcore_data, cid):

View file

@ -19,13 +19,13 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_LIGHTS, CONF_EXCLUDE)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyvera==0.2.38']
REQUIREMENTS = ['pyvera==0.2.39']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'vera'
VERA_CONTROLLER = None
VERA_CONTROLLER = 'vera_controller'
CONF_CONTROLLER = 'vera_controller_url'
@ -34,7 +34,8 @@ VERA_ID_FORMAT = '{}_{}'
ATTR_CURRENT_POWER_W = "current_power_w"
ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh"
VERA_DEVICES = defaultdict(list)
VERA_DEVICES = 'vera_devices'
VERA_SCENES = 'vera_scenes'
VERA_ID_LIST_SCHEMA = vol.Schema([int])
@ -47,20 +48,20 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
VERA_COMPONENTS = [
'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'climate', 'cover'
'binary_sensor', 'sensor', 'light', 'switch',
'lock', 'climate', 'cover', 'scene'
]
# pylint: disable=unused-argument, too-many-function-args
def setup(hass, base_config):
"""Set up for Vera devices."""
global VERA_CONTROLLER
import pyvera as veraApi
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions")
VERA_CONTROLLER.stop()
hass.data[VERA_CONTROLLER].stop()
config = base_config.get(DOMAIN)
@ -70,11 +71,14 @@ def setup(hass, base_config):
exclude_ids = config.get(CONF_EXCLUDE)
# Initialize the Vera controller.
VERA_CONTROLLER, _ = veraApi.init_controller(base_url)
controller, _ = veraApi.init_controller(base_url)
hass.data[VERA_CONTROLLER] = controller
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
try:
all_devices = VERA_CONTROLLER.get_devices()
all_devices = controller.get_devices()
all_scenes = controller.get_scenes()
except RequestException:
# There was a network related error connecting to the Vera controller.
_LOGGER.exception("Error communicating with Vera API")
@ -84,12 +88,19 @@ def setup(hass, base_config):
devices = [device for device in all_devices
if device.device_id not in exclude_ids]
vera_devices = defaultdict(list)
for device in devices:
device_type = map_vera_device(device, light_ids)
if device_type is None:
continue
VERA_DEVICES[device_type].append(device)
vera_devices[device_type].append(device)
hass.data[VERA_DEVICES] = vera_devices
vera_scenes = []
for scene in all_scenes:
vera_scenes.append(scene)
hass.data[VERA_SCENES] = vera_scenes
for component in VERA_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, base_config)

View file

@ -26,11 +26,13 @@ REQUIREMENTS = ['volvooncall==0.4.0']
_LOGGER = logging.getLogger(__name__)
CONF_UPDATE_INTERVAL = 'update_interval'
MIN_UPDATE_INTERVAL = timedelta(minutes=1)
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
CONF_UPDATE_INTERVAL = 'update_interval'
CONF_REGION = 'region'
CONF_SERVICE_URL = 'service_url'
CONF_SCANDINAVIAN_MILES = 'scandinavian_miles'
SIGNAL_VEHICLE_SEEN = '{}.vehicle_seen'.format(DOMAIN)
@ -41,6 +43,8 @@ RESOURCES = {'position': ('device_tracker',),
'fuel_amount': ('sensor', 'Fuel amount', 'mdi:gas-station', 'L'),
'fuel_amount_level': (
'sensor', 'Fuel level', 'mdi:water-percent', '%'),
'average_fuel_consumption': (
'sensor', 'Fuel consumption', 'mdi:gas-station', 'L/100 km'),
'distance_to_empty': ('sensor', 'Range', 'mdi:ruler', 'km'),
'washer_fluid_level': ('binary_sensor', 'Washer fluid'),
'brake_fluid': ('binary_sensor', 'Brake Fluid'),
@ -61,6 +65,7 @@ CONFIG_SCHEMA = vol.Schema({
cv.ensure_list, [vol.In(RESOURCES)]),
vol.Optional(CONF_REGION): cv.string,
vol.Optional(CONF_SERVICE_URL): cv.string,
vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
@ -123,7 +128,8 @@ class VolvoData:
"""Initialize the component state."""
self.entities = {}
self.vehicles = {}
self.names = config[DOMAIN].get(CONF_NAME)
self.config = config[DOMAIN]
self.names = self.config.get(CONF_NAME)
def vehicle_name(self, vehicle):
"""Provide a friendly name for a vehicle."""

View file

@ -219,7 +219,9 @@ class XiaomiDevice(Entity):
def push_data(self, data):
"""Push from Hub."""
_LOGGER.debug("PUSH >> %s: %s", self, data)
if self.parse_data(data) or self.parse_voltage(data):
is_data = self.parse_data(data)
is_voltage = self.parse_voltage(data)
if is_data or is_voltage:
self.schedule_update_ha_state()
def parse_voltage(self, data):

View file

@ -110,6 +110,9 @@ sensor:
tts:
- platform: google
# Cloud
cloud:
group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml

View file

@ -1,8 +1,8 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 59
PATCH_VERSION = '2'
MINOR_VERSION = 60
PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 4, 2)
@ -52,6 +52,7 @@ CONF_CURRENCY = 'currency'
CONF_CUSTOMIZE = 'customize'
CONF_CUSTOMIZE_DOMAIN = 'customize_domain'
CONF_CUSTOMIZE_GLOB = 'customize_glob'
CONF_DELAY_TIME = 'delay_time'
CONF_DEVICE = 'device'
CONF_DEVICE_CLASS = 'device_class'
CONF_DEVICES = 'devices'
@ -74,6 +75,7 @@ CONF_EXCLUDE = 'exclude'
CONF_FILE_PATH = 'file_path'
CONF_FILENAME = 'filename'
CONF_FOR = 'for'
CONF_FORCE_UPDATE = 'force_update'
CONF_FRIENDLY_NAME = 'friendly_name'
CONF_HEADERS = 'headers'
CONF_HOST = 'host'

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