Compare commits
239 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 50e5032f86 | |||
| 1d615ea6c3 | |||
| 2e1b1635b1 | |||
| fe7dca5144 | |||
| fdeef2f707 | |||
| 2ec0d25a38 | |||
| fb5019e73f | |||
| 1e276a7b07 | |||
| d72a181e30 | |||
| 698d133455 | |||
| 0dccef4063 | |||
| feb85b90b4 | |||
| 48909539be | |||
| 2355216f61 | |||
| 55a44b0a1c | |||
| 27b0d648a6 | |||
| 90724847a3 | |||
| 90fb33f610 | |||
| cb59b3fee1 | |||
| 90689c38f7 | |||
| fd6fd765b2 | |||
| 06a20d0d15 | |||
| 252aea37d2 | |||
| 5a3a43cd5b | |||
| dd0ca0adc4 | |||
| c77d2ea341 | |||
| 4a3be6d514 | |||
| da2cb8e97e | |||
| 42fcaf9a75 | |||
| af8aec001c | |||
| f6c5e5ff00 | |||
| 398735c9be | |||
| 8ceeee032c | |||
| 54f01f3f11 | |||
| bc549e9525 | |||
| 4bb78097a7 | |||
| 7c380588a0 | |||
| f7daefd7a5 | |||
| 97e6a69adb | |||
| fe7384a4ef | |||
| 3c9e09ce16 | |||
| c3d548a0dd | |||
| ebd64cded9 | |||
| fee89d8d16 | |||
| b3d16e8f89 | |||
| c059dfdb67 | |||
| d153ee0b9f | |||
| 0f9ae8827c | |||
| 5d52993231 | |||
| 84025e46ff | |||
| bf66019c66 | |||
| a748b5ee5e | |||
| 98370560e1 | |||
| 597f53ae30 | |||
| 7ac1e469b7 | |||
| ecc249aa27 | |||
| 6215e27de4 | |||
| b282167f26 | |||
| c278209c7b | |||
| 427d7ee1fc | |||
| 55234a7fa3 | |||
| 3765f882c7 | |||
| b75ce4f1b2 | |||
| 95663f8126 | |||
| f114263845 | |||
| e7ce110dc6 | |||
| 3342db33e4 | |||
| 0fb281c5b3 | |||
| 2dab239021 | |||
| 95c57412ff | |||
| eb42d59210 | |||
| 6507cc1dc8 | |||
| 1892eb654f | |||
| 5309006494 | |||
| e2920ce5e5 | |||
| 19d1d748d4 | |||
| 8d661f8dea | |||
| 9363b189ba | |||
| 4da876e5c2 | |||
| 335008ae5c | |||
| a4da31b573 | |||
| cd795489ca | |||
| a8a037db49 | |||
| fc8e8e5d8c | |||
| 56597d290c | |||
| 8fcec03adf | |||
| a0ddb24245 | |||
| 23273d3e88 | |||
| 74adebc2fd | |||
| 4b3a932d88 | |||
| cbe5225e04 | |||
| 811fdc5533 | |||
| c92e5c147a | |||
| 73d6227021 | |||
| 79f45b5176 | |||
| b18679ec0b | |||
| 7d566c2c3d | |||
| 46d9d77d03 | |||
| 4a98b32a03 | |||
| adbcbe3a67 | |||
| eef3dda1e9 | |||
| 08899ade00 | |||
| 5814fdadd0 | |||
| daf7d9ea7f | |||
| 956543ae1e | |||
| fbb6782081 | |||
| 369caeedbd | |||
| 489a02b2c2 | |||
| c4550d02c5 | |||
| 49733b7fdf | |||
| 0999e2ddc4 | |||
| d427063acd | |||
| ff3a4637a4 | |||
| 8523aaca64 | |||
| e3236d1a3b | |||
| d7e8616651 | |||
| c0663bf722 | |||
| d195fd47f7 | |||
| e84ff61d4a | |||
| 317bc10ccb | |||
| 1cb42087f9 | |||
| b035577cf5 | |||
| 55c84eaee3 | |||
| eb6017e16c | |||
| 19ee3c42b6 | |||
| af70054692 | |||
| be94f6e939 | |||
| f513f6271e | |||
| 588b36dff2 | |||
| cc5893ed8b | |||
| 124a6cc8c0 | |||
| 0fe4245620 | |||
| 289c88ff71 | |||
| 57f3bed465 | |||
| 62e86270e6 | |||
| 3aceca9d8a | |||
| e81b3f7bc0 | |||
| cc6c2bf25e | |||
| 9575cbde09 | |||
| 4e79517971 | |||
| 4ec4cfc44e | |||
| d74f4eaf52 | |||
| 5696e38dd6 | |||
| c6aaacbb08 | |||
| d8ca04a4bc | |||
| ac9c1235bb | |||
| c49cce7243 | |||
| 99a20c845c | |||
| 3723f67dc1 | |||
| b655fe6e04 | |||
| 24e9fa238a | |||
| 83afd12807 | |||
| 82a7dffc03 | |||
| c11b6798dc | |||
| 8e4c799ad1 | |||
| 5059d4c54b | |||
| 569d9764ab | |||
| 058deb5be3 | |||
| cd36a71f64 | |||
| 6832a2e642 | |||
| 2c7b2fe19e | |||
| 45ec7f6180 | |||
| cb8517834a | |||
| f41ef5d727 | |||
| a221b10694 | |||
| 6e1785173f | |||
| 52cff83267 | |||
| e49b970665 | |||
| a0530d8b9c | |||
| 99d4021f47 | |||
| 7f0d0607f1 | |||
| cf298c2435 | |||
| 77cdc833f0 | |||
| 96f8c37dcd | |||
| 5b4e30cde3 | |||
| d4dfb4d80c | |||
| c895f1f1db | |||
| 944af9cd7d | |||
| f3e16ca304 | |||
| 6de38cb941 | |||
| 8e51e66c9b | |||
| 57dfe378a1 | |||
| d8cded637c | |||
| 7c92f7e1ad | |||
| ccf0559059 | |||
| 2d38e70268 | |||
| 9dae1ca5c2 | |||
| 6ac8caa857 | |||
| 39131d06ba | |||
| 8a626e1572 | |||
| bc376f7045 | |||
| cad1de790e | |||
| 32b7f4d16f | |||
| 1adb5040e7 | |||
| 47dad547eb | |||
| 86c06ad76e | |||
| 7dbcf63543 | |||
| 6ff340492b | |||
| 50cd6c9a9c | |||
| 365f21b209 | |||
| 075422e7ad | |||
| 342ec8ec99 | |||
| 2b59b917c4 | |||
| e40388e7ad | |||
| cb292a0b18 | |||
| 33663f9502 | |||
| e57d6f679a | |||
| 775185896a | |||
| e6331aafb2 | |||
| 455ac9724a | |||
| e6be560e00 | |||
| 91b062f9b7 | |||
| 9919eec596 | |||
| e525d13a5d | |||
| ce67be2fff | |||
| 53048f71a0 | |||
| 164e953e8c | |||
| 7156e4782e | |||
| 37fef4016e | |||
| cee49f313f | |||
| 05330ac763 | |||
| 6884965c80 | |||
| e992527c68 | |||
| 431a381c8d | |||
| 22088d192a | |||
| 418a8bab11 | |||
| a94e7ec25d | |||
| 8ac63fd70c | |||
| 8ba9e8016b | |||
| 750ea44b4b | |||
| 78428b0acd | |||
| 72db28abac | |||
| 80ab02c3e8 | |||
| 0bde0a6f3a | |||
| d1b73a96f4 | |||
| 1749859cdf | |||
| 931f4d8161 | |||
| 61508deed3 | |||
| 1b57566e8e |
+28
-4
@@ -8,6 +8,9 @@ omit =
|
||||
homeassistant/helpers/signal.py
|
||||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/abode.py
|
||||
homeassistant/components/*/abode.py
|
||||
|
||||
homeassistant/components/alarmdecoder.py
|
||||
homeassistant/components/*/alarmdecoder.py
|
||||
|
||||
@@ -29,6 +32,9 @@ omit =
|
||||
homeassistant/components/arlo.py
|
||||
homeassistant/components/*/arlo.py
|
||||
|
||||
homeassistant/components/asterisk_mbox.py
|
||||
homeassistant/components/*/asterisk_mbox.py
|
||||
|
||||
homeassistant/components/axis.py
|
||||
homeassistant/components/*/axis.py
|
||||
|
||||
@@ -172,7 +178,10 @@ omit =
|
||||
homeassistant/components/twilio.py
|
||||
homeassistant/components/notify/twilio_sms.py
|
||||
homeassistant/components/notify/twilio_call.py
|
||||
|
||||
|
||||
homeassistant/components/usps.py
|
||||
homeassistant/components/*/usps.py
|
||||
|
||||
homeassistant/components/velbus.py
|
||||
homeassistant/components/*/velbus.py
|
||||
|
||||
@@ -197,7 +206,11 @@ omit =
|
||||
homeassistant/components/*/wink.py
|
||||
|
||||
homeassistant/components/xiaomi.py
|
||||
homeassistant/components/*/xiaomi.py
|
||||
homeassistant/components/binary_sensor/xiaomi.py
|
||||
homeassistant/components/cover/xiaomi.py
|
||||
homeassistant/components/light/xiaomi.py
|
||||
homeassistant/components/sensor/xiaomi.py
|
||||
homeassistant/components/switch/xiaomi.py
|
||||
|
||||
homeassistant/components/zabbix.py
|
||||
homeassistant/components/*/zabbix.py
|
||||
@@ -214,6 +227,7 @@ omit =
|
||||
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/concord232.py
|
||||
homeassistant/components/alarm_control_panel/egardia.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py
|
||||
@@ -264,6 +278,7 @@ omit =
|
||||
homeassistant/components/device_tracker/cisco_ios.py
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/gpslogger.py
|
||||
homeassistant/components/device_tracker/huawei_router.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
homeassistant/components/device_tracker/linksys_ap.py
|
||||
homeassistant/components/device_tracker/linksys_smart.py
|
||||
@@ -297,6 +312,7 @@ omit =
|
||||
homeassistant/components/light/blinkt.py
|
||||
homeassistant/components/light/blinksticklight.py
|
||||
homeassistant/components/light/decora.py
|
||||
homeassistant/components/light/decora_wifi.py
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/hue.py
|
||||
homeassistant/components/light/hyperion.py
|
||||
@@ -316,12 +332,14 @@ omit =
|
||||
homeassistant/components/light/yeelightsunflower.py
|
||||
homeassistant/components/light/zengge.py
|
||||
homeassistant/components/lirc.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
|
||||
homeassistant/components/media_player/aquostv.py
|
||||
homeassistant/components/media_player/bluesound.py
|
||||
homeassistant/components/media_player/braviatv.py
|
||||
homeassistant/components/media_player/cast.py
|
||||
homeassistant/components/media_player/clementine.py
|
||||
@@ -351,6 +369,7 @@ omit =
|
||||
homeassistant/components/media_player/pioneer.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/roku.py
|
||||
homeassistant/components/media_player/russound_rio.py
|
||||
homeassistant/components/media_player/russound_rnet.py
|
||||
homeassistant/components/media_player/samsungtv.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
@@ -371,6 +390,7 @@ omit =
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/group.py
|
||||
homeassistant/components/notify/hipchat.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/kodi.py
|
||||
homeassistant/components/notify/lannouncer.py
|
||||
@@ -379,6 +399,7 @@ omit =
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nfandroidtv.py
|
||||
homeassistant/components/notify/nma.py
|
||||
homeassistant/components/notify/prowl.py
|
||||
homeassistant/components/notify/pushbullet.py
|
||||
homeassistant/components/notify/pushetta.py
|
||||
homeassistant/components/notify/pushover.py
|
||||
@@ -437,6 +458,7 @@ omit =
|
||||
homeassistant/components/sensor/fixer.py
|
||||
homeassistant/components/sensor/fritzbox_callmonitor.py
|
||||
homeassistant/components/sensor/fritzbox_netmonitor.py
|
||||
homeassistant/components/sensor/geizhals.py
|
||||
homeassistant/components/sensor/gitter.py
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/google_travel_time.py
|
||||
@@ -483,6 +505,7 @@ omit =
|
||||
homeassistant/components/sensor/scrape.py
|
||||
homeassistant/components/sensor/sensehat.py
|
||||
homeassistant/components/sensor/serial_pm.py
|
||||
homeassistant/components/sensor/shodan.py
|
||||
homeassistant/components/sensor/skybeacon.py
|
||||
homeassistant/components/sensor/sma.py
|
||||
homeassistant/components/sensor/snmp.py
|
||||
@@ -503,9 +526,9 @@ omit =
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/ups.py
|
||||
homeassistant/components/sensor/usps.py
|
||||
homeassistant/components/sensor/vasttrafik.py
|
||||
homeassistant/components/sensor/waqi.py
|
||||
homeassistant/components/sensor/worldtidesinfo.py
|
||||
homeassistant/components/sensor/xbox_live.py
|
||||
homeassistant/components/sensor/yweather.py
|
||||
homeassistant/components/sensor/zamg.py
|
||||
@@ -527,17 +550,18 @@ omit =
|
||||
homeassistant/components/switch/orvibo.py
|
||||
homeassistant/components/switch/pilight.py
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rainmachine.py
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/rpi_rf.py
|
||||
homeassistant/components/switch/tplink.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/wake_on_lan.py
|
||||
homeassistant/components/switch/xiaomi_vacuum.py
|
||||
homeassistant/components/telegram_bot/*
|
||||
homeassistant/components/thingspeak.py
|
||||
homeassistant/components/tts/amazon_polly.py
|
||||
homeassistant/components/tts/picotts.py
|
||||
homeassistant/components/upnp.py
|
||||
homeassistant/components/vacuum/roomba.py
|
||||
homeassistant/components/weather/bom.py
|
||||
homeassistant/components/weather/buienradar.py
|
||||
homeassistant/components/weather/metoffice.py
|
||||
|
||||
@@ -73,6 +73,7 @@ pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
venv
|
||||
.venv
|
||||
Pipfile*
|
||||
|
||||
# vimmy stuff
|
||||
*.swp
|
||||
@@ -93,3 +94,6 @@ docs/build
|
||||
|
||||
# Windows Explorer
|
||||
desktop.ini
|
||||
/home-assistant.pyproj
|
||||
/home-assistant.sln
|
||||
/.vs/home-assistant/v14
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
|
||||
|
||||
The process is straight-forward.
|
||||
|
||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0)
|
||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
|
||||
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
|
||||
- Write the code for your device, notification service, sensor, or IoT thing.
|
||||
- Ensure tests work.
|
||||
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
|
||||
|
||||
Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details.
|
||||
Still interested? Then you should take a peek at the [developer documentation](https://home-assistant.io/developers/) to get more details.
|
||||
|
||||
|
||||
+1
-2
@@ -29,8 +29,7 @@ COPY requirements_all.txt requirements_all.txt
|
||||
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
|
||||
# See PR #8103 for more info.
|
||||
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
|
||||
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet && \
|
||||
pip3 uninstall -y enum34
|
||||
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
This component provides basic support for Abode Home Security system.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME
|
||||
|
||||
REQUIREMENTS = ['abodepy==0.7.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
DOMAIN = 'abode'
|
||||
DEFAULT_NAME = 'Abode'
|
||||
DATA_ABODE = 'data_abode'
|
||||
DEFAULT_ENTITY_NAMESPACE = 'abode'
|
||||
|
||||
NOTIFICATION_ID = 'abode_notification'
|
||||
NOTIFICATION_TITLE = 'Abode Security Setup'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up Abode component."""
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
try:
|
||||
data = AbodeData(username, password)
|
||||
hass.data[DATA_ABODE] = data
|
||||
|
||||
for component in ['binary_sensor', 'alarm_control_panel']:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Abode: %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
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AbodeData:
|
||||
"""Shared Abode data."""
|
||||
|
||||
def __init__(self, username, password):
|
||||
"""Initialize Abode oject."""
|
||||
import abodepy
|
||||
|
||||
self.abode = abodepy.Abode(username, password)
|
||||
self.devices = self.abode.get_devices()
|
||||
|
||||
_LOGGER.debug("Abode Security set up with %s devices",
|
||||
len(self.devices))
|
||||
@@ -13,7 +13,8 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_NIGHT)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
@@ -31,6 +32,7 @@ SERVICE_TO_METHOD = {
|
||||
SERVICE_ALARM_DISARM: 'alarm_disarm',
|
||||
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
|
||||
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
|
||||
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
|
||||
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
|
||||
}
|
||||
|
||||
@@ -81,6 +83,18 @@ def alarm_arm_away(hass, code=None, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_night(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm night."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_trigger(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for trigger."""
|
||||
@@ -187,6 +201,17 @@ class AlarmControlPanel(Entity):
|
||||
"""
|
||||
return self.hass.async_add_job(self.alarm_arm_away, code)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.alarm_arm_night, code)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
This component provides HA alarm_control_panel support for Abode System.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (DATA_ABODE, DEFAULT_NAME)
|
||||
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ICON = 'mdi:security'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
data = hass.data.get(DATA_ABODE)
|
||||
|
||||
add_devices([AbodeAlarm(hass, data, data.abode.get_alarm())])
|
||||
|
||||
|
||||
class AbodeAlarm(alarm.AlarmControlPanel):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
def __init__(self, hass, data, device):
|
||||
"""Initialize the alarm control panel."""
|
||||
super(AbodeAlarm, self).__init__()
|
||||
self._device = device
|
||||
self._name = "{0}".format(DEFAULT_NAME)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self._device.mode == "standby":
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif self._device.mode == "away":
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
elif self._device.mode == "home":
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
state = None
|
||||
return state
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._device.set_standby()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
self._device.set_home()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
self._device.set_away()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def update(self):
|
||||
"""Update the device state."""
|
||||
self._device.refresh()
|
||||
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Interfaces with Egardia/Woonveilig alarm control panel.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.egardia/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.exceptions as exc
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
|
||||
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
|
||||
|
||||
REQUIREMENTS = ['pythonegardia==1.0.18']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_REPORT_SERVER_CODES = 'report_server_codes'
|
||||
CONF_REPORT_SERVER_ENABLED = 'report_server_enabled'
|
||||
CONF_REPORT_SERVER_PORT = 'report_server_port'
|
||||
|
||||
DEFAULT_NAME = 'Egardia'
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_REPORT_SERVER_ENABLED = False
|
||||
DEFAULT_REPORT_SERVER_PORT = 85
|
||||
DOMAIN = 'egardia'
|
||||
|
||||
NOTIFICATION_ID = 'egardia_notification'
|
||||
NOTIFICATION_TITLE = 'Egardia'
|
||||
|
||||
STATES = {
|
||||
'ARM': STATE_ALARM_ARMED_AWAY,
|
||||
'DAY HOME': STATE_ALARM_ARMED_HOME,
|
||||
'DISARM': STATE_ALARM_DISARMED,
|
||||
'HOME': STATE_ALARM_ARMED_HOME,
|
||||
'TRIGGERED': STATE_ALARM_TRIGGERED,
|
||||
'UNKNOWN': STATE_UNKNOWN,
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_REPORT_SERVER_CODES): vol.All(cv.ensure_list),
|
||||
vol.Optional(CONF_REPORT_SERVER_ENABLED,
|
||||
default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean,
|
||||
vol.Optional(CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT):
|
||||
cv.port,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Egardia platform."""
|
||||
from pythonegardia import egardiadevice
|
||||
|
||||
name = config.get(CONF_NAME)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
rs_enabled = config.get(CONF_REPORT_SERVER_ENABLED)
|
||||
rs_port = config.get(CONF_REPORT_SERVER_PORT)
|
||||
rs_codes = config.get(CONF_REPORT_SERVER_CODES)
|
||||
|
||||
try:
|
||||
egardiasystem = egardiadevice.EgardiaDevice(
|
||||
host, port, username, password, '')
|
||||
except requests.exceptions.RequestException:
|
||||
raise exc.PlatformNotReady()
|
||||
except egardiadevice.UnauthorizedError:
|
||||
_LOGGER.error("Unable to authorize. Wrong password or username")
|
||||
return False
|
||||
|
||||
add_devices([EgardiaAlarm(
|
||||
name, egardiasystem, hass, rs_enabled, rs_port, rs_codes)], True)
|
||||
|
||||
|
||||
class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Egardia alarm."""
|
||||
|
||||
def __init__(self, name, egardiasystem, hass, rs_enabled=False,
|
||||
rs_port=None, rs_codes=None):
|
||||
"""Initialize object."""
|
||||
self._name = name
|
||||
self._egardiasystem = egardiasystem
|
||||
self._status = STATE_UNKNOWN
|
||||
self._rs_enabled = rs_enabled
|
||||
self._rs_port = rs_port
|
||||
self._hass = hass
|
||||
|
||||
if rs_codes is not None:
|
||||
self._rs_codes = rs_codes[0]
|
||||
else:
|
||||
self._rs_codes = rs_codes
|
||||
|
||||
if self._rs_enabled:
|
||||
self.listen_to_system_status()
|
||||
|
||||
@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._status
|
||||
|
||||
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)
|
||||
|
||||
def listen_to_system_status(self):
|
||||
"""Subscribe to egardia_system_status event."""
|
||||
self._hass.bus.listen(
|
||||
'egardia_system_status', self.handle_system_status_event)
|
||||
|
||||
def lookupstatusfromcode(self, statuscode):
|
||||
"""Look at the rs_codes and returns the status from the code."""
|
||||
status = 'UNKNOWN'
|
||||
if self._rs_codes is not None:
|
||||
statuscode = str(statuscode).strip()
|
||||
for i in self._rs_codes:
|
||||
val = str(self._rs_codes[i]).strip()
|
||||
if ',' in val:
|
||||
splitted = val.split(',')
|
||||
for code in splitted:
|
||||
code = str(code).strip()
|
||||
if statuscode == code:
|
||||
status = i.upper()
|
||||
break
|
||||
elif statuscode == val:
|
||||
status = i.upper()
|
||||
break
|
||||
return status
|
||||
|
||||
def parsestatus(self, status):
|
||||
"""Parse the status."""
|
||||
newstatus = ([v for k, v in STATES.items()
|
||||
if status.upper() == k][0])
|
||||
self._status = newstatus
|
||||
|
||||
def update(self):
|
||||
"""Update the alarm status."""
|
||||
status = self._egardiasystem.getstate()
|
||||
self.parsestatus(status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_disarm()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending disarm command: %s", err)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_arm_home()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending arm home command: %s", err)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_arm_away()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending arm away command: %s", err)
|
||||
@@ -12,9 +12,10 @@ import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
|
||||
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
|
||||
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)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
@@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self._state in (STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY) and \
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_NIGHT) and \
|
||||
self._pending_time and self._state_ts + self._pending_time > \
|
||||
dt_util.utcnow():
|
||||
return STATE_ALARM_PENDING
|
||||
@@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_NIGHT
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._pending_time:
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command. No code needed."""
|
||||
self._pre_trigger_state = self._state
|
||||
|
||||
@@ -31,6 +31,17 @@ alarm_arm_away:
|
||||
description: An optional code to arm away the alarm control panel with
|
||||
example: 1234
|
||||
|
||||
alarm_arm_night:
|
||||
description: Send the alarm the command for arm night
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm night
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm night the alarm control panel with
|
||||
example: 1234
|
||||
|
||||
alarm_trigger:
|
||||
description: Send the alarm the command for trigger
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.3']
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -89,11 +89,11 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
status = self.simplisafe.state()
|
||||
if status == 'Off':
|
||||
if status == 'off':
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif status == 'Home':
|
||||
elif status == 'home':
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif status == 'Away':
|
||||
elif status == 'away':
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
|
||||
@@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
|
||||
CONF_NAME)
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
|
||||
|
||||
REQUIREMENTS = ['total_connect_client==0.11']
|
||||
|
||||
@@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif status == self._client.ARMED_AWAY:
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
elif status == self._client.ARMED_STAY_NIGHT:
|
||||
state = STATE_ALARM_ARMED_NIGHT
|
||||
elif status == self._client.ARMING:
|
||||
state = STATE_ALARM_ARMING
|
||||
elif status == self._client.DISARMING:
|
||||
state = STATE_ALARM_DISARMING
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
|
||||
@@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
self._client.arm_away()
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
self._client.arm_stay_night()
|
||||
|
||||
@@ -128,19 +128,18 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
alexa_intent_info = req.get('intent')
|
||||
alexa_response = AlexaResponse(hass, alexa_intent_info)
|
||||
|
||||
if req_type == 'LaunchRequest':
|
||||
alexa_response.add_speech(
|
||||
SpeechType.plaintext,
|
||||
"Hello, and welcome to the future. How may I help?")
|
||||
return self.json(alexa_response)
|
||||
|
||||
if req_type != 'IntentRequest':
|
||||
if req_type != 'IntentRequest' and req_type != 'LaunchRequest':
|
||||
_LOGGER.warning('Received unsupported request: %s', req_type)
|
||||
return self.json_message(
|
||||
'Received unsupported request: {}'.format(req_type),
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
intent_name = alexa_intent_info['name']
|
||||
if req_type == 'LaunchRequest':
|
||||
intent_name = data.get('session', {}) \
|
||||
.get('application', {}) \
|
||||
.get('applicationId')
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
|
||||
@@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials):
|
||||
hass.async_add_job(configurator.request_done, instance)
|
||||
|
||||
instance = configurator.request_config(
|
||||
hass, 'Apple TV Authentication', configuration_callback,
|
||||
'Apple TV Authentication', configuration_callback,
|
||||
description='Please enter PIN code shown on screen.',
|
||||
submit_caption='Confirm',
|
||||
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Support for Asterisk Voicemail interface."""
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import (CONF_HOST,
|
||||
CONF_PORT, CONF_PASSWORD)
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
|
||||
async_dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['asterisk_mbox==0.4.0']
|
||||
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
|
||||
DOMAIN = 'asterisk_mbox'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up for the Asterisk Voicemail box."""
|
||||
conf = config.get(DOMAIN)
|
||||
|
||||
host = conf.get(CONF_HOST)
|
||||
port = conf.get(CONF_PORT)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
|
||||
|
||||
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AsteriskData(object):
|
||||
"""Store Asterisk mailbox data."""
|
||||
|
||||
def __init__(self, hass, host, port, password):
|
||||
"""Init the Asterisk data object."""
|
||||
from asterisk_mbox import Client as asteriskClient
|
||||
|
||||
self.hass = hass
|
||||
self.client = asteriskClient(host, port, password, self.handle_data)
|
||||
self.messages = []
|
||||
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages)
|
||||
|
||||
@callback
|
||||
def handle_data(self, command, msg):
|
||||
"""Handle changes to the mailbox."""
|
||||
from asterisk_mbox.commands import CMD_MESSAGE_LIST
|
||||
|
||||
if command == CMD_MESSAGE_LIST:
|
||||
_LOGGER.info("AsteriskVM sent updated message list")
|
||||
self.messages = sorted(msg,
|
||||
key=lambda item: item['info']['origtime'],
|
||||
reverse=True)
|
||||
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
|
||||
self.messages)
|
||||
|
||||
@callback
|
||||
def _request_messages(self):
|
||||
"""Handle changes to the mailbox."""
|
||||
_LOGGER.info("Requesting message list")
|
||||
self.client.messages()
|
||||
@@ -27,7 +27,6 @@ from homeassistant.helpers.restore_state import async_get_last_state
|
||||
from homeassistant.loader import get_platform
|
||||
from homeassistant.util.dt import utcnow
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.frontend import register_built_in_panel
|
||||
|
||||
DOMAIN = 'automation'
|
||||
DEPENDENCIES = ['group']
|
||||
@@ -232,10 +231,6 @@ def async_setup(hass, config):
|
||||
DOMAIN, service, turn_onoff_service_handler,
|
||||
descriptions.get(service), schema=SERVICE_SCHEMA)
|
||||
|
||||
if 'frontend' in hass.config.components:
|
||||
register_built_in_panel(hass, 'automation', 'Automations',
|
||||
'mdi:playlist-play')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber):
|
||||
|
||||
title = '{} ({})'.format(name, host)
|
||||
request_id = configurator.request_config(
|
||||
hass, title, configuration_callback,
|
||||
title, configuration_callback,
|
||||
description='Functionality: ' + str(AXIS_INCLUDE),
|
||||
entity_picture="/static/images/logo_axis.png",
|
||||
link_name='Axis platform documentation',
|
||||
|
||||
@@ -14,7 +14,6 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
from homeassistant.helpers.deprecation import deprecated_substitute
|
||||
|
||||
DOMAIN = 'binary_sensor'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
@@ -66,7 +65,6 @@ class BinarySensorDevice(Entity):
|
||||
return STATE_ON if self.is_on else STATE_OFF
|
||||
|
||||
@property
|
||||
@deprecated_substitute('sensor_class')
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
This component provides HA binary_sensor support for Abode Security System.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (CONF_ATTRIBUTION, DATA_ABODE)
|
||||
from homeassistant.const import (ATTR_ATTRIBUTION)
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Sensor types: Name, device_class
|
||||
SENSOR_TYPES = {
|
||||
'Door Contact': 'opening',
|
||||
'Motion Camera': 'motion',
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
data = hass.data.get(DATA_ABODE)
|
||||
|
||||
sensors = []
|
||||
for sensor in data.devices:
|
||||
_LOGGER.debug('Sensor type %s', sensor.type)
|
||||
if sensor.type in ['Door Contact', 'Motion Camera']:
|
||||
sensors.append(AbodeBinarySensor(hass, data, sensor))
|
||||
|
||||
_LOGGER.debug('Adding %d sensors', len(sensors))
|
||||
add_devices(sensors)
|
||||
|
||||
|
||||
class AbodeBinarySensor(BinarySensorDevice):
|
||||
"""A binary sensor implementation for Abode device."""
|
||||
|
||||
def __init__(self, hass, data, device):
|
||||
"""Initialize a sensor for Abode device."""
|
||||
super(AbodeBinarySensor, self).__init__()
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "{0} {1}".format(self._device.type, self._device.name)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
if self._device.type == 'Door Contact':
|
||||
return self._device.status != 'Closed'
|
||||
elif self._device.type == 'Motion Camera':
|
||||
return self._device.get_value('motion_event') == '1'
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the binary sensor."""
|
||||
return SENSOR_TYPES.get(self._device.type)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attrs = {}
|
||||
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||
attrs['device_id'] = self._device.device_id
|
||||
attrs['battery_low'] = self._device.battery_low
|
||||
|
||||
return attrs
|
||||
|
||||
def update(self):
|
||||
"""Update the device state."""
|
||||
self._device.refresh()
|
||||
@@ -20,9 +20,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up an Online Status binary sensor."""
|
||||
add_entities((OnlineStatus(config, apcupsd.DATA),))
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up an APCUPSd Online Status binary sensor."""
|
||||
add_devices([OnlineStatus(config, apcupsd.DATA)], True)
|
||||
|
||||
|
||||
class OnlineStatus(BinarySensorDevice):
|
||||
@@ -33,7 +33,6 @@ class OnlineStatus(BinarySensorDevice):
|
||||
self._config = config
|
||||
self._data = data
|
||||
self._state = None
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -13,10 +13,9 @@ import voluptuous as vol
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS)
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,7 +25,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_RESOURCE): cv.url,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_PIN): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -35,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the aREST binary sensor."""
|
||||
resource = config.get(CONF_RESOURCE)
|
||||
pin = config.get(CONF_PIN)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
try:
|
||||
response = requests.get(resource, timeout=10).json()
|
||||
|
||||
@@ -37,7 +37,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
for device in bloomsky.BLOOMSKY.devices.values():
|
||||
for variable in sensors:
|
||||
add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)])
|
||||
add_devices(
|
||||
[BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True)
|
||||
|
||||
|
||||
class BloomSkySensor(BinarySensorDevice):
|
||||
@@ -50,7 +51,7 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
self._sensor_name = sensor_name
|
||||
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
|
||||
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
|
||||
self.update()
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -4,19 +4,18 @@ Support for custom shell commands to retrieve values.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.command_line/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.sensor.command_line import CommandSensorData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_COMMAND, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
CONF_COMMAND, CONF_DEVICE_CLASS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
})
|
||||
@@ -44,15 +42,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
command = config.get(CONF_COMMAND)
|
||||
payload_off = config.get(CONF_PAYLOAD_OFF)
|
||||
payload_on = config.get(CONF_PAYLOAD_ON)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
data = CommandSensorData(command)
|
||||
data = CommandSensorData(hass, command)
|
||||
|
||||
add_devices([CommandBinarySensor(
|
||||
hass, data, name, device_class, payload_on, payload_off,
|
||||
value_template)])
|
||||
value_template)], True)
|
||||
|
||||
|
||||
class CommandBinarySensor(BinarySensorDevice):
|
||||
@@ -69,7 +67,6 @@ class CommandBinarySensor(BinarySensorDevice):
|
||||
self._payload_on = payload_on
|
||||
self._payload_off = payload_off
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -72,9 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
)
|
||||
)
|
||||
|
||||
add_devices(sensors)
|
||||
|
||||
return True
|
||||
add_devices(sensors, True)
|
||||
|
||||
|
||||
def get_opening_type(zone):
|
||||
@@ -100,7 +98,6 @@ class Concord232ZoneSensor(BinarySensorDevice):
|
||||
self._zone = zone
|
||||
self._number = zone['number']
|
||||
self._zone_type = zone_type
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@@ -130,7 +127,7 @@ class Concord232ZoneSensor(BinarySensorDevice):
|
||||
if last_update > datetime.timedelta(seconds=1):
|
||||
self._client.zones = self._client.list_zones()
|
||||
self._client.last_zone_update = datetime.datetime.now()
|
||||
_LOGGER.debug("Updated from Zone: %s", self._zone['name'])
|
||||
_LOGGER.debug("Updated from zone: %s", self._zone['name'])
|
||||
|
||||
if hasattr(self._client, 'zones'):
|
||||
self._zone = next((x for x in self._client.zones
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.components.digital_ocean import (
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'Droplet'
|
||||
DEFAULT_SENSOR_CLASS = 'moving'
|
||||
DEFAULT_DEVICE_CLASS = 'moving'
|
||||
DEPENDENCIES = ['digital_ocean']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
@@ -69,7 +69,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEFAULT_SENSOR_CLASS
|
||||
return DEFAULT_DEVICE_CLASS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
||||
@@ -26,7 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
dev.append(EcobeeBinarySensor(sensor['name'], index))
|
||||
|
||||
add_devices(dev)
|
||||
add_devices(dev, True)
|
||||
|
||||
|
||||
class EcobeeBinarySensor(BinarySensorDevice):
|
||||
@@ -39,7 +39,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
|
||||
self.index = sensor_index
|
||||
self._state = None
|
||||
self._device_class = 'occupancy'
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -12,9 +12,8 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.components import enocean
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_ID, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_NAME, CONF_ID, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,7 +23,6 @@ DEFAULT_NAME = 'EnOcean binary sensor'
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -33,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Binary Sensor platform for EnOcean."""
|
||||
dev_id = config.get(CONF_ID)
|
||||
devname = config.get(CONF_NAME)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
add_devices([EnOceanBinarySensor(dev_id, devname, device_class)])
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ class IssBinarySensor(BinarySensorDevice):
|
||||
self._state = None
|
||||
self._name = name
|
||||
self._show_on_map = show
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -15,10 +15,9 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
|
||||
CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_DEVICE_CLASS)
|
||||
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -49,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
async_add_devices([MqttBinarySensor(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),
|
||||
config.get(CONF_DEVICE_CLASS),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_PAYLOAD_ON),
|
||||
config.get(CONF_PAYLOAD_OFF),
|
||||
|
||||
@@ -4,62 +4,27 @@ Support for MySensors binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mysensors/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES,
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
|
||||
BinarySensorDevice)
|
||||
from homeassistant.const import STATE_ON
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = []
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the MySensors platform for sensors."""
|
||||
# Only act if loaded via mysensors by discovery event.
|
||||
# Otherwise gateway is not setup.
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
|
||||
if not gateways:
|
||||
return
|
||||
|
||||
for gateway in gateways:
|
||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||
# states. Map them in a dict of lists.
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_DOOR: [set_req.V_TRIPPED],
|
||||
pres.S_MOTION: [set_req.V_TRIPPED],
|
||||
pres.S_SMOKE: [set_req.V_TRIPPED],
|
||||
}
|
||||
if float(gateway.protocol_version) >= 1.5:
|
||||
map_sv_types.update({
|
||||
pres.S_SPRINKLER: [set_req.V_TRIPPED],
|
||||
pres.S_WATER_LEAK: [set_req.V_TRIPPED],
|
||||
pres.S_SOUND: [set_req.V_TRIPPED],
|
||||
pres.S_VIBRATION: [set_req.V_TRIPPED],
|
||||
pres.S_MOISTURE: [set_req.V_TRIPPED],
|
||||
})
|
||||
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
map_sv_types, devices, MySensorsBinarySensor, add_devices))
|
||||
"""Setup the mysensors platform for binary sensors."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
|
||||
add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(
|
||||
mysensors.MySensorsDeviceEntity, BinarySensorDevice):
|
||||
mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Represent the value of a MySensors Binary Sensor child node."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
if self.value_type in self._values:
|
||||
return self._values[self.value_type] == STATE_ON
|
||||
return False
|
||||
return self._values.get(self.value_type) == STATE_ON
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
|
||||
@@ -44,18 +44,19 @@ CONF_WELCOME_SENSORS = 'welcome_sensors'
|
||||
CONF_PRESENCE_SENSORS = 'presence_sensors'
|
||||
CONF_TAG_SENSORS = 'tag_sensors'
|
||||
|
||||
DEFAULT_TIMEOUT = 15
|
||||
DEFAULT_OFFSET = 90
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOME): cv.string,
|
||||
vol.Optional(CONF_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_OFFSET): cv.positive_int,
|
||||
vol.Optional(CONF_CAMERAS, default=[]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(
|
||||
CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES.keys()):
|
||||
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
|
||||
vol.Optional(
|
||||
CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES.keys()):
|
||||
vol.Optional(CONF_HOME): cv.string,
|
||||
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int,
|
||||
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
|
||||
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES):
|
||||
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
@@ -63,16 +64,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the access to Netatmo binary sensor."""
|
||||
netatmo = get_component('netatmo')
|
||||
home = config.get(CONF_HOME, None)
|
||||
timeout = config.get(CONF_TIMEOUT, 15)
|
||||
offset = config.get(CONF_OFFSET, 90)
|
||||
home = config.get(CONF_HOME)
|
||||
timeout = config.get(CONF_TIMEOUT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
|
||||
module_name = None
|
||||
|
||||
import lnetatmo
|
||||
try:
|
||||
data = CameraData(netatmo.NETATMO_AUTH, home)
|
||||
if data.get_camera_names() == []:
|
||||
if not data.get_camera_names():
|
||||
return None
|
||||
except lnetatmo.NoDevice:
|
||||
return None
|
||||
@@ -93,7 +94,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
for variable in welcome_sensors:
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout,
|
||||
offset, camera_type, variable)])
|
||||
offset, camera_type, variable)], True)
|
||||
if camera_type == 'NOC':
|
||||
if CONF_CAMERAS in config:
|
||||
if config[CONF_CAMERAS] != [] and \
|
||||
@@ -102,14 +103,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
for variable in presence_sensors:
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout, offset,
|
||||
camera_type, variable)])
|
||||
camera_type, variable)], True)
|
||||
|
||||
for module_name in data.get_module_names(camera_name):
|
||||
for variable in tag_sensors:
|
||||
camera_type = None
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout, offset,
|
||||
camera_type, variable)])
|
||||
camera_type, variable)], True)
|
||||
|
||||
|
||||
class NetatmoBinarySensor(BinarySensorDevice):
|
||||
@@ -137,7 +138,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
|
||||
self._name, camera_id)
|
||||
self._cameratype = camera_type
|
||||
self.update()
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
|
||||
SENSOR_TYPES[octo_type][1], 'flags')
|
||||
devices.append(new_sensor)
|
||||
add_devices(devices)
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
@@ -69,8 +69,6 @@ class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
self.api_endpoint = endpoint
|
||||
self.api_group = group
|
||||
self.api_tool = tool
|
||||
# Set initial state
|
||||
self.update()
|
||||
_LOGGER.debug("Created OctoPrint binary sensor %r", self)
|
||||
|
||||
@property
|
||||
|
||||
@@ -28,7 +28,7 @@ CONF_PING_COUNT = 'count'
|
||||
|
||||
DEFAULT_NAME = 'Ping Binary sensor'
|
||||
DEFAULT_PING_COUNT = 5
|
||||
DEFAULT_SENSOR_CLASS = 'connectivity'
|
||||
DEFAULT_DEVICE_CLASS = 'connectivity'
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
@@ -73,7 +73,7 @@ class PingBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEFAULT_SENSOR_CLASS
|
||||
return DEFAULT_DEVICE_CLASS
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -14,11 +14,10 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.components.sensor.rest import RestData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
|
||||
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
@@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
headers = config.get(CONF_HEADERS)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
@@ -74,7 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
return False
|
||||
|
||||
add_devices([RestBinarySensor(
|
||||
hass, rest, name, device_class, value_template)])
|
||||
hass, rest, name, device_class, value_template)], True)
|
||||
|
||||
|
||||
class RestBinarySensor(BinarySensorDevice):
|
||||
@@ -89,7 +87,6 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
self._state = False
|
||||
self._previous_data = None
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -15,11 +15,9 @@ from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS,
|
||||
EVENT_HOMEASSISTANT_START, STATE_ON)
|
||||
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers.restore_state import async_get_last_state
|
||||
@@ -30,7 +28,6 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -49,8 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
value_template.extract_entities())
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
device_class = get_deprecated(
|
||||
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
@@ -13,10 +13,9 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS,
|
||||
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN,
|
||||
ATTR_ENTITY_ID, CONF_DEVICE_CLASS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -38,7 +37,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
|
||||
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -50,7 +48,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
name = config.get(CONF_NAME)
|
||||
threshold = config.get(CONF_THRESHOLD)
|
||||
limit_type = config.get(CONF_TYPE)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
async_add_devices(
|
||||
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
|
||||
|
||||
@@ -16,9 +16,7 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
|
||||
DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_SENSOR_CLASS,
|
||||
CONF_DEVICE_CLASS, STATE_UNKNOWN,)
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, STATE_UNKNOWN)
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
|
||||
@@ -32,7 +30,6 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(CONF_INVERT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -50,8 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
entity_id = device_config[ATTR_ENTITY_ID]
|
||||
attribute = device_config.get(CONF_ATTRIBUTE)
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
device_class = get_deprecated(
|
||||
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||
invert = device_config[CONF_INVERT]
|
||||
|
||||
sensors.append(
|
||||
|
||||
@@ -34,5 +34,5 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from SENSOR_CLASSES."""
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
return 'safety'
|
||||
|
||||
@@ -6,13 +6,12 @@ https://home-assistant.io/components/binary_sensor.workday/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import datetime
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME, WEEKDAYS
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@@ -39,11 +38,14 @@ CONF_EXCLUDES = 'excludes'
|
||||
DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday']
|
||||
DEFAULT_NAME = 'Workday Sensor'
|
||||
ALLOWED_DAYS = WEEKDAYS + ['holiday']
|
||||
CONF_OFFSET = 'days_offset'
|
||||
DEFAULT_OFFSET = 0
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
|
||||
vol.Optional(CONF_PROVINCE, default=None): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
|
||||
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
|
||||
vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]),
|
||||
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES):
|
||||
@@ -60,8 +62,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
province = config.get(CONF_PROVINCE)
|
||||
workdays = config.get(CONF_WORKDAYS)
|
||||
excludes = config.get(CONF_EXCLUDES)
|
||||
days_offset = config.get(CONF_OFFSET)
|
||||
|
||||
year = datetime.datetime.now().year
|
||||
year = (datetime.now() + timedelta(days=days_offset)).year
|
||||
obj_holidays = getattr(holidays, country)(years=year)
|
||||
|
||||
if province:
|
||||
@@ -85,7 +88,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
_LOGGER.debug("%s %s", date, name)
|
||||
|
||||
add_devices([IsWorkdaySensor(
|
||||
obj_holidays, workdays, excludes, sensor_name)], True)
|
||||
obj_holidays, workdays, excludes, days_offset, sensor_name)], True)
|
||||
|
||||
|
||||
def day_to_string(day):
|
||||
@@ -99,12 +102,13 @@ def day_to_string(day):
|
||||
class IsWorkdaySensor(BinarySensorDevice):
|
||||
"""Implementation of a Workday sensor."""
|
||||
|
||||
def __init__(self, obj_holidays, workdays, excludes, name):
|
||||
def __init__(self, obj_holidays, workdays, excludes, days_offset, name):
|
||||
"""Initialize the Workday sensor."""
|
||||
self._name = name
|
||||
self._obj_holidays = obj_holidays
|
||||
self._workdays = workdays
|
||||
self._excludes = excludes
|
||||
self._days_offset = days_offset
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
@@ -135,6 +139,16 @@ class IsWorkdaySensor(BinarySensorDevice):
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the attributes of the entity."""
|
||||
# return self._attributes
|
||||
return {
|
||||
CONF_WORKDAYS: self._workdays,
|
||||
CONF_EXCLUDES: self._excludes,
|
||||
CONF_OFFSET: self._days_offset
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Get date and look whether it is a holiday."""
|
||||
@@ -142,11 +156,12 @@ class IsWorkdaySensor(BinarySensorDevice):
|
||||
self._state = False
|
||||
|
||||
# Get iso day of the week (1 = Monday, 7 = Sunday)
|
||||
day = datetime.datetime.today().isoweekday() - 1
|
||||
date = datetime.today() + timedelta(days=self._days_offset)
|
||||
day = date.isoweekday() - 1
|
||||
day_of_week = day_to_string(day)
|
||||
|
||||
if self.is_include(day_of_week, dt_util.now()):
|
||||
if self.is_include(day_of_week, date):
|
||||
self._state = True
|
||||
|
||||
if self.is_exclude(day_of_week, dt_util.now()):
|
||||
if self.is_exclude(day_of_week, date):
|
||||
self._state = False
|
||||
|
||||
@@ -23,9 +23,7 @@ def get_device(values, **kwargs):
|
||||
"""Create Z-Wave entity device."""
|
||||
device_mapping = workaround.get_device_mapping(values.primary)
|
||||
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
|
||||
# Default the multiplier to 4
|
||||
re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4
|
||||
return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8)
|
||||
return ZWaveTriggerSensor(values, "motion")
|
||||
|
||||
if workaround.get_device_component_mapping(values.primary) == DOMAIN:
|
||||
return ZWaveBinarySensor(values, None)
|
||||
@@ -62,15 +60,21 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
|
||||
class ZWaveTriggerSensor(ZWaveBinarySensor):
|
||||
"""Representation of a stateless sensor within Z-Wave."""
|
||||
|
||||
def __init__(self, values, device_class, re_arm_sec=60):
|
||||
def __init__(self, values, device_class):
|
||||
"""Initialize the sensor."""
|
||||
super(ZWaveTriggerSensor, self).__init__(values, device_class)
|
||||
self.re_arm_sec = re_arm_sec
|
||||
# Set default off delay to 60 sec
|
||||
self.re_arm_sec = 60
|
||||
self.invalidate_after = None
|
||||
|
||||
def update_properties(self):
|
||||
"""Handle value changes for this entity's node."""
|
||||
self._state = self.values.primary.data
|
||||
_LOGGER.debug('off_delay=%s', self.values.off_delay)
|
||||
# Set re_arm_sec if off_delay is provided from the sensor
|
||||
if self.values.off_delay:
|
||||
_LOGGER.debug('off_delay.data=%s', self.values.off_delay.data)
|
||||
self.re_arm_sec = self.values.off_delay.data * 8
|
||||
# only allow this value to be true for re_arm secs
|
||||
if not self.hass:
|
||||
return
|
||||
|
||||
@@ -6,7 +6,6 @@ https://home-assistant.io/components/camera.foscam/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
|
||||
@@ -16,11 +15,15 @@ from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['pyfoscam==1.2']
|
||||
|
||||
CONF_IP = 'ip'
|
||||
|
||||
DEFAULT_NAME = 'Foscam Camera'
|
||||
DEFAULT_PORT = 88
|
||||
|
||||
FOSCAM_COMM_ERROR = -8
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_IP): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
@@ -33,46 +36,60 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a Foscam IP Camera."""
|
||||
add_devices([FoscamCamera(config)])
|
||||
add_devices([FoscamCam(config)])
|
||||
|
||||
|
||||
class FoscamCamera(Camera):
|
||||
class FoscamCam(Camera):
|
||||
"""An implementation of a Foscam IP camera."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a Foscam camera."""
|
||||
super(FoscamCamera, self).__init__()
|
||||
super(FoscamCam, self).__init__()
|
||||
|
||||
ip_address = device_info.get(CONF_IP)
|
||||
port = device_info.get(CONF_PORT)
|
||||
|
||||
self._base_url = 'http://{}:{}/'.format(ip_address, port)
|
||||
|
||||
uri_template = self._base_url \
|
||||
+ 'cgi-bin/CGIProxy.fcgi?' \
|
||||
+ 'cmd=snapPicture2&usr={}&pwd={}'
|
||||
|
||||
self._username = device_info.get(CONF_USERNAME)
|
||||
self._password = device_info.get(CONF_PASSWORD)
|
||||
self._snap_picture_url = uri_template.format(
|
||||
self._username,
|
||||
self._password
|
||||
)
|
||||
self._name = device_info.get(CONF_NAME)
|
||||
self._motion_status = False
|
||||
|
||||
_LOGGER.info("Using the following URL for %s: %s",
|
||||
self._name, uri_template.format('***', '***'))
|
||||
from foscam import FoscamCamera
|
||||
|
||||
self._foscam_session = FoscamCamera(ip_address, port, self._username,
|
||||
self._password, verbose=False)
|
||||
|
||||
def camera_image(self):
|
||||
"""Return a still image reponse from the camera."""
|
||||
# Send the request to snap a picture and return raw jpg data
|
||||
# Handle exception if host is not reachable or url failed
|
||||
try:
|
||||
response = requests.get(self._snap_picture_url, timeout=10)
|
||||
except requests.exceptions.ConnectionError:
|
||||
result, response = self._foscam_session.snap_picture_2()
|
||||
if result == FOSCAM_COMM_ERROR:
|
||||
return None
|
||||
|
||||
return response
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Camera Motion Detection Status."""
|
||||
return self._motion_status
|
||||
|
||||
def enable_motion_detection(self):
|
||||
"""Enable motion detection in camera."""
|
||||
ret, err = self._foscam_session.enable_motion_detection()
|
||||
if ret == FOSCAM_COMM_ERROR:
|
||||
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
|
||||
self._motion_status = True
|
||||
else:
|
||||
return response.content
|
||||
self._motion_status = False
|
||||
|
||||
def disable_motion_detection(self):
|
||||
"""Disable motion detection."""
|
||||
ret, err = self._foscam_session.disable_motion_detection()
|
||||
if ret == FOSCAM_COMM_ERROR:
|
||||
_LOGGER.debug("Unable to communicate with Foscam Camera: %s", err)
|
||||
self._motion_status = True
|
||||
else:
|
||||
self._motion_status = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Support for a camera made up of usps mail images.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.usps/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.components.usps import DATA_USPS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['usps']
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up USPS mail camera."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
usps = hass.data[DATA_USPS]
|
||||
add_devices([USPSCamera(usps)])
|
||||
|
||||
|
||||
class USPSCamera(Camera):
|
||||
"""Representation of the images available from USPS."""
|
||||
|
||||
def __init__(self, usps):
|
||||
"""Initialize the USPS camera images."""
|
||||
super().__init__()
|
||||
|
||||
self._usps = usps
|
||||
self._name = self._usps.name
|
||||
self._session = self._usps.session
|
||||
|
||||
self._mail_img = []
|
||||
self._last_mail = None
|
||||
self._mail_index = 0
|
||||
self._mail_count = 0
|
||||
|
||||
self._timer = None
|
||||
|
||||
def camera_image(self):
|
||||
"""Update the camera's image if it has changed."""
|
||||
self._usps.update()
|
||||
try:
|
||||
self._mail_count = len(self._usps.mail)
|
||||
except TypeError:
|
||||
# No mail
|
||||
return None
|
||||
|
||||
if self._usps.mail != self._last_mail:
|
||||
# Mail items must have changed
|
||||
self._mail_img = []
|
||||
if len(self._usps.mail) >= 1:
|
||||
self._last_mail = self._usps.mail
|
||||
for article in self._usps.mail:
|
||||
_LOGGER.debug("Fetching article image: %s", article)
|
||||
img = self._session.get(article['image']).content
|
||||
self._mail_img.append(img)
|
||||
|
||||
try:
|
||||
return self._mail_img[self._mail_index]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return '{} mail'.format(self._name)
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""Return date of mail as model."""
|
||||
try:
|
||||
return 'Date: {}'.format(self._usps.mail[0]['date'])
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Update the mail image index periodically."""
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
"""Update mail image index."""
|
||||
if self._mail_index < (self._mail_count - 1):
|
||||
self._mail_index += 1
|
||||
else:
|
||||
self._mail_index = 0
|
||||
@@ -86,13 +86,17 @@ SET_AUX_HEAT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_AUX_HEAT): cv.boolean,
|
||||
})
|
||||
SET_TEMPERATURE_SCHEMA = vol.Schema({
|
||||
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_OPERATION_MODE): cv.string,
|
||||
})
|
||||
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
|
||||
cv.has_at_least_one_key(
|
||||
ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW),
|
||||
{
|
||||
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_OPERATION_MODE): cv.string,
|
||||
}
|
||||
))
|
||||
SET_FAN_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_FAN_MODE): cv.string,
|
||||
|
||||
@@ -75,7 +75,8 @@ def _setup_round(username, password, config, add_devices):
|
||||
zones = evo_api.temperatures(force_refresh=True)
|
||||
for i, zone in enumerate(zones):
|
||||
add_devices(
|
||||
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
|
||||
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)],
|
||||
True
|
||||
)
|
||||
except socket.error:
|
||||
_LOGGER.error(
|
||||
@@ -115,9 +116,9 @@ def _setup_us(username, password, config, add_devices):
|
||||
class RoundThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell Round Connected thermostat."""
|
||||
|
||||
def __init__(self, device, zone_id, master, away_temp):
|
||||
def __init__(self, client, zone_id, master, away_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.device = device
|
||||
self.client = client
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._name = 'round connected'
|
||||
@@ -126,7 +127,6 @@ class RoundThermostat(ClimateDevice):
|
||||
self._is_dhw = False
|
||||
self._away_temp = away_temp
|
||||
self._away = False
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -155,12 +155,12 @@ class RoundThermostat(ClimateDevice):
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
self.device.set_temperature(self._name, temperature)
|
||||
self.client.set_temperature(self._name, temperature)
|
||||
|
||||
@property
|
||||
def current_operation(self: ClimateDevice) -> str:
|
||||
"""Get the current operation of the system."""
|
||||
return getattr(self.device, ATTR_SYSTEM_MODE, None)
|
||||
return getattr(self.client, ATTR_SYSTEM_MODE, None)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
@@ -169,8 +169,8 @@ class RoundThermostat(ClimateDevice):
|
||||
|
||||
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
|
||||
"""Set the HVAC mode for the thermostat."""
|
||||
if hasattr(self.device, ATTR_SYSTEM_MODE):
|
||||
self.device.system_mode = operation_mode
|
||||
if hasattr(self.client, ATTR_SYSTEM_MODE):
|
||||
self.client.system_mode = operation_mode
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on.
|
||||
@@ -180,19 +180,19 @@ class RoundThermostat(ClimateDevice):
|
||||
it doesn't get overwritten when away mode is switched on.
|
||||
"""
|
||||
self._away = True
|
||||
self.device.set_temperature(self._name, self._away_temp)
|
||||
self.client.set_temperature(self._name, self._away_temp)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self._away = False
|
||||
self.device.cancel_temp_override(self._name)
|
||||
self.client.cancel_temp_override(self._name)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest date."""
|
||||
try:
|
||||
# Only refresh if this is the "master" device,
|
||||
# others will pick up the cache
|
||||
for val in self.device.temperatures(force_refresh=self._master):
|
||||
for val in self.client.temperatures(force_refresh=self._master):
|
||||
if val['id'] == self._id:
|
||||
data = val
|
||||
|
||||
@@ -210,6 +210,12 @@ class RoundThermostat(ClimateDevice):
|
||||
self._name = data['name']
|
||||
self._is_dhw = False
|
||||
|
||||
# The underlying library doesn't expose the thermostat's mode
|
||||
# but we can pull it out of the big dictionary of information.
|
||||
device = self.client.devices[self._id]
|
||||
self.client.system_mode = device[
|
||||
'thermostat']['changeableValues']['mode']
|
||||
|
||||
|
||||
class HoneywellUSThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell US Thermostat."""
|
||||
|
||||
@@ -4,15 +4,11 @@ MySensors platform that offers a Climate (MySensors-HVAC) component.
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/climate.mysensors/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.climate import (
|
||||
STATE_COOL, STATE_HEAT, STATE_OFF, STATE_AUTO, ClimateDevice,
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
|
||||
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
DICT_HA_TO_MYS = {
|
||||
STATE_AUTO: 'AutoChangeOver',
|
||||
@@ -29,28 +25,12 @@ DICT_MYS_TO_HA = {
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the mysensors climate."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
|
||||
if not gateways:
|
||||
return
|
||||
|
||||
for gateway in gateways:
|
||||
if float(gateway.protocol_version) < 1.5:
|
||||
continue
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_HVAC: [set_req.V_HVAC_FLOW_STATE],
|
||||
}
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
map_sv_types, devices, MySensorsHVAC, add_devices))
|
||||
"""Setup the mysensors climate."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
|
||||
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
"""Representation of a MySensors HVAC."""
|
||||
|
||||
@property
|
||||
@@ -84,26 +64,28 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
|
||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
|
||||
if temp is None:
|
||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
|
||||
return float(temp)
|
||||
return float(temp) if temp is not None else None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the highbound target temperature we try to reach."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
|
||||
return float(self._values.get(set_req.V_HVAC_SETPOINT_COOL))
|
||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
|
||||
return float(temp) if temp is not None else None
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lowbound target temperature we try to reach."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_HVAC_SETPOINT_COOL in self._values:
|
||||
return float(self._values.get(set_req.V_HVAC_SETPOINT_HEAT))
|
||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
|
||||
return float(temp) if temp is not None else None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._values.get(self.gateway.const.SetReq.V_HVAC_FLOW_STATE)
|
||||
return self._values.get(self.value_type)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
@@ -128,7 +110,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
|
||||
high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
heat = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
|
||||
cool = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
|
||||
updates = ()
|
||||
updates = []
|
||||
if temp is not None:
|
||||
if heat is not None:
|
||||
# Set HEAT Target temperature
|
||||
@@ -146,7 +128,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, value_type, value)
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
# optimistically assume that device has changed state
|
||||
self._values[value_type] = value
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@@ -156,54 +138,22 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
# optimistically assume that device has changed state
|
||||
self._values[set_req.V_HVAC_SPEED] = fan
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target temperature."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, set_req.V_HVAC_FLOW_STATE,
|
||||
self.node_id, self.child_id, self.value_type,
|
||||
DICT_HA_TO_MYS[operation_mode])
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
|
||||
# optimistically assume that device has changed state
|
||||
self._values[self.value_type] = operation_mode
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
child = node.children[self.child_id]
|
||||
for value_type, value in child.values.items():
|
||||
_LOGGER.debug(
|
||||
"%s: value_type %s, value = %s", self._name, value_type, value)
|
||||
if value_type == set_req.V_HVAC_FLOW_STATE:
|
||||
self._values[value_type] = DICT_MYS_TO_HA[value]
|
||||
else:
|
||||
self._values[value_type] = value
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Set new target humidity."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxillary heater on."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxillary heater off."""
|
||||
_LOGGER.error("Service Not Implemented yet")
|
||||
super().update()
|
||||
self._values[self.value_type] = DICT_MYS_TO_HA[
|
||||
self._values[self.value_type]]
|
||||
|
||||
@@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the NetAtmo Thermostat."""
|
||||
netatmo = get_component('netatmo')
|
||||
device = config.get(CONF_RELAY)
|
||||
@@ -49,7 +49,7 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
if config[CONF_THERMOSTAT] != [] and \
|
||||
module_name not in config[CONF_THERMOSTAT]:
|
||||
continue
|
||||
add_callback_devices([NetatmoThermostat(data, module_name)])
|
||||
add_devices([NetatmoThermostat(data, module_name)], True)
|
||||
except lnetatmo.NoDevice:
|
||||
return None
|
||||
|
||||
@@ -64,7 +64,6 @@ class NetatmoThermostat(ClimateDevice):
|
||||
self._name = module_name
|
||||
self._target_temperature = None
|
||||
self._away = None
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
||||
host)
|
||||
|
||||
add_devices(tstats)
|
||||
add_devices(tstats, True)
|
||||
|
||||
|
||||
class RadioThermostat(ClimateDevice):
|
||||
@@ -89,7 +89,6 @@ class RadioThermostat(ClimateDevice):
|
||||
self._away = False
|
||||
self._away_temps = away_temps
|
||||
self._prev_temp = None
|
||||
self.update()
|
||||
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
|
||||
|
||||
@property
|
||||
|
||||
@@ -288,7 +288,7 @@ class TadoClimate(ClimateDevice):
|
||||
|
||||
if 'setting' in overlay_data:
|
||||
setting_data = overlay_data['setting']
|
||||
setting = setting is not None
|
||||
setting = setting_data is not None
|
||||
|
||||
if setting:
|
||||
if 'mode' in setting_data:
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
|
||||
|
||||
DOMAIN = 'config'
|
||||
DEPENDENCIES = ['http']
|
||||
SECTIONS = ('core', 'group', 'hassbian', 'automation')
|
||||
SECTIONS = ('core', 'group', 'hassbian', 'automation', 'script')
|
||||
ON_DEMAND = ('zwave')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Provide configuration end points for scripts."""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.config import EditKeyBasedConfigView
|
||||
from homeassistant.components.script import SCRIPT_ENTRY_SCHEMA, async_reload
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
CONFIG_PATH = 'scripts.yaml'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass):
|
||||
"""Set up the script config API."""
|
||||
hass.http.register_view(EditKeyBasedConfigView(
|
||||
'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA,
|
||||
post_write_hook=async_reload
|
||||
))
|
||||
return True
|
||||
@@ -1,12 +1,17 @@
|
||||
"""Provide configuration end points for Z-Wave."""
|
||||
import asyncio
|
||||
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.const import HTTP_NOT_FOUND
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.config import EditKeyBasedConfigView
|
||||
from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY
|
||||
from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
CONFIG_PATH = 'zwave_device_config.yaml'
|
||||
OZW_LOG_FILENAME = 'OZW_Log.txt'
|
||||
URL_API_OZW_LOG = '/api/zwave/ozwlog'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -16,4 +21,123 @@ def async_setup(hass):
|
||||
'zwave', 'device_config', CONFIG_PATH, cv.entity_id,
|
||||
DEVICE_CONFIG_SCHEMA_ENTRY
|
||||
))
|
||||
hass.http.register_view(ZWaveNodeValueView)
|
||||
hass.http.register_view(ZWaveNodeGroupView)
|
||||
hass.http.register_view(ZWaveNodeConfigView)
|
||||
hass.http.register_view(ZWaveUserCodeView)
|
||||
hass.http.register_static_path(
|
||||
URL_API_OZW_LOG, hass.config.path(OZW_LOG_FILENAME), False)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ZWaveNodeValueView(HomeAssistantView):
|
||||
"""View to return the node values."""
|
||||
|
||||
url = r"/api/zwave/values/{node_id:\d+}"
|
||||
name = "api:zwave:values"
|
||||
|
||||
@ha.callback
|
||||
def get(self, request, node_id):
|
||||
"""Retrieve groups of node."""
|
||||
nodeid = int(node_id)
|
||||
hass = request.app['hass']
|
||||
values_list = hass.data[const.DATA_ENTITY_VALUES]
|
||||
|
||||
values_data = {}
|
||||
# Return a list of values for this node that are used as a
|
||||
# primary value for an entity
|
||||
for entity_values in values_list:
|
||||
if entity_values.primary.node.node_id != nodeid:
|
||||
continue
|
||||
|
||||
values_data[entity_values.primary.value_id] = {
|
||||
'label': entity_values.primary.label,
|
||||
'index': entity_values.primary.index,
|
||||
'instance': entity_values.primary.instance,
|
||||
}
|
||||
return self.json(values_data)
|
||||
|
||||
|
||||
class ZWaveNodeGroupView(HomeAssistantView):
|
||||
"""View to return the nodes group configuration."""
|
||||
|
||||
url = r"/api/zwave/groups/{node_id:\d+}"
|
||||
name = "api:zwave:groups"
|
||||
|
||||
@ha.callback
|
||||
def get(self, request, node_id):
|
||||
"""Retrieve groups of node."""
|
||||
nodeid = int(node_id)
|
||||
hass = request.app['hass']
|
||||
network = hass.data.get(const.DATA_NETWORK)
|
||||
node = network.nodes.get(nodeid)
|
||||
if node is None:
|
||||
return self.json_message('Node not found', HTTP_NOT_FOUND)
|
||||
groupdata = node.groups
|
||||
groups = {}
|
||||
for key, value in groupdata.items():
|
||||
groups[key] = {'associations': value.associations,
|
||||
'association_instances':
|
||||
value.associations_instances,
|
||||
'label': value.label,
|
||||
'max_associations': value.max_associations}
|
||||
return self.json(groups)
|
||||
|
||||
|
||||
class ZWaveNodeConfigView(HomeAssistantView):
|
||||
"""View to return the nodes configuration options."""
|
||||
|
||||
url = r"/api/zwave/config/{node_id:\d+}"
|
||||
name = "api:zwave:config"
|
||||
|
||||
@ha.callback
|
||||
def get(self, request, node_id):
|
||||
"""Retrieve configurations of node."""
|
||||
nodeid = int(node_id)
|
||||
hass = request.app['hass']
|
||||
network = hass.data.get(const.DATA_NETWORK)
|
||||
node = network.nodes.get(nodeid)
|
||||
if node is None:
|
||||
return self.json_message('Node not found', HTTP_NOT_FOUND)
|
||||
config = {}
|
||||
for value in (
|
||||
node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION)
|
||||
.values()):
|
||||
config[value.index] = {'label': value.label,
|
||||
'type': value.type,
|
||||
'help': value.help,
|
||||
'data_items': value.data_items,
|
||||
'data': value.data,
|
||||
'max': value.max,
|
||||
'min': value.min}
|
||||
return self.json(config)
|
||||
|
||||
|
||||
class ZWaveUserCodeView(HomeAssistantView):
|
||||
"""View to return the nodes usercode configuration."""
|
||||
|
||||
url = r"/api/zwave/usercodes/{node_id:\d+}"
|
||||
name = "api:zwave:usercodes"
|
||||
|
||||
@ha.callback
|
||||
def get(self, request, node_id):
|
||||
"""Retrieve usercodes of node."""
|
||||
nodeid = int(node_id)
|
||||
hass = request.app['hass']
|
||||
network = hass.data.get(const.DATA_NETWORK)
|
||||
node = network.nodes.get(nodeid)
|
||||
if node is None:
|
||||
return self.json_message('Node not found', HTTP_NOT_FOUND)
|
||||
usercodes = {}
|
||||
if not node.has_command_class(const.COMMAND_CLASS_USER_CODE):
|
||||
return self.json(usercodes)
|
||||
for value in (
|
||||
node.get_values(class_id=const.COMMAND_CLASS_USER_CODE)
|
||||
.values()):
|
||||
if value.genre != const.GENRE_USER:
|
||||
continue
|
||||
usercodes[value.index] = {'code': value.data,
|
||||
'label': value.label,
|
||||
'length': len(value.data)}
|
||||
return self.json(usercodes)
|
||||
|
||||
@@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when
|
||||
the user has submitted configuration information.
|
||||
"""
|
||||
import asyncio
|
||||
import functools as ft
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback as async_callback
|
||||
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
|
||||
ATTR_ENTITY_PICTURE
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_REQUESTS = {}
|
||||
_KEY_INSTANCE = 'configurator'
|
||||
|
||||
DATA_REQUESTS = 'configurator_requests'
|
||||
|
||||
ATTR_CONFIGURE_ID = 'configure_id'
|
||||
ATTR_DESCRIPTION = 'description'
|
||||
ATTR_DESCRIPTION_IMAGE = 'description_image'
|
||||
@@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured'
|
||||
|
||||
|
||||
@bind_hass
|
||||
def request_config(
|
||||
hass, name, callback, description=None, description_image=None,
|
||||
@async_callback
|
||||
def async_request_config(
|
||||
hass, name, callback=None, description=None, description_image=None,
|
||||
submit_caption=None, fields=None, link_name=None, link_url=None,
|
||||
entity_picture=None):
|
||||
"""Create a new request for configuration.
|
||||
|
||||
Will return an ID to be used for sequent calls.
|
||||
"""
|
||||
instance = run_callback_threadsafe(hass.loop,
|
||||
_async_get_instance,
|
||||
hass).result()
|
||||
instance = hass.data.get(_KEY_INSTANCE)
|
||||
|
||||
request_id = instance.request_config(
|
||||
if instance is None:
|
||||
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
|
||||
|
||||
request_id = instance.async_request_config(
|
||||
name, callback,
|
||||
description, description_image, submit_caption,
|
||||
fields, link_name, link_url, entity_picture)
|
||||
|
||||
_REQUESTS[request_id] = instance
|
||||
if DATA_REQUESTS not in hass.data:
|
||||
hass.data[DATA_REQUESTS] = {}
|
||||
|
||||
hass.data[DATA_REQUESTS][request_id] = instance
|
||||
|
||||
return request_id
|
||||
|
||||
|
||||
def notify_errors(request_id, error):
|
||||
@bind_hass
|
||||
def request_config(hass, *args, **kwargs):
|
||||
"""Create a new request for configuration.
|
||||
|
||||
Will return an ID to be used for sequent calls.
|
||||
"""
|
||||
return run_callback_threadsafe(
|
||||
hass.loop, ft.partial(async_request_config, hass, *args, **kwargs)
|
||||
).result()
|
||||
|
||||
|
||||
@bind_hass
|
||||
@async_callback
|
||||
def async_notify_errors(hass, request_id, error):
|
||||
"""Add errors to a config request."""
|
||||
try:
|
||||
_REQUESTS[request_id].notify_errors(request_id, error)
|
||||
hass.data[DATA_REQUESTS][request_id].async_notify_errors(
|
||||
request_id, error)
|
||||
except KeyError:
|
||||
# If request_id does not exist
|
||||
pass
|
||||
|
||||
|
||||
def request_done(request_id):
|
||||
@bind_hass
|
||||
def notify_errors(hass, request_id, error):
|
||||
"""Add errors to a config request."""
|
||||
return run_callback_threadsafe(
|
||||
hass.loop, async_notify_errors, hass, request_id, error
|
||||
).result()
|
||||
|
||||
|
||||
@bind_hass
|
||||
@async_callback
|
||||
def async_request_done(hass, request_id):
|
||||
"""Mark a configuration request as done."""
|
||||
try:
|
||||
_REQUESTS.pop(request_id).request_done(request_id)
|
||||
hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id)
|
||||
except KeyError:
|
||||
# If request_id does not exist
|
||||
pass
|
||||
|
||||
|
||||
@bind_hass
|
||||
def request_done(hass, request_id):
|
||||
"""Mark a configuration request as done."""
|
||||
return run_callback_threadsafe(
|
||||
hass.loop, async_request_done, hass, request_id
|
||||
).result()
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up the configurator component."""
|
||||
return True
|
||||
|
||||
|
||||
@async_callback
|
||||
def _async_get_instance(hass):
|
||||
"""Get an instance per hass object."""
|
||||
instance = hass.data.get(_KEY_INSTANCE)
|
||||
|
||||
if instance is None:
|
||||
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class Configurator(object):
|
||||
"""The class to keep track of current configuration requests."""
|
||||
|
||||
@@ -105,14 +133,16 @@ class Configurator(object):
|
||||
self._cur_id = 0
|
||||
self._requests = {}
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
|
||||
DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call)
|
||||
|
||||
def request_config(
|
||||
@async_callback
|
||||
def async_request_config(
|
||||
self, name, callback,
|
||||
description, description_image, submit_caption,
|
||||
fields, link_name, link_url, entity_picture):
|
||||
"""Set up a request for configuration."""
|
||||
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
|
||||
entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, name, hass=self.hass)
|
||||
|
||||
if fields is None:
|
||||
fields = []
|
||||
@@ -138,11 +168,12 @@ class Configurator(object):
|
||||
] if value is not None
|
||||
})
|
||||
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
|
||||
self.hass.states.async_set(entity_id, STATE_CONFIGURE, data)
|
||||
|
||||
return request_id
|
||||
|
||||
def notify_errors(self, request_id, error):
|
||||
@async_callback
|
||||
def async_notify_errors(self, request_id, error):
|
||||
"""Update the state with errors."""
|
||||
if not self._validate_request_id(request_id):
|
||||
return
|
||||
@@ -154,9 +185,10 @@ class Configurator(object):
|
||||
new_data = dict(state.attributes)
|
||||
new_data[ATTR_ERRORS] = error
|
||||
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
||||
self.hass.states.async_set(entity_id, STATE_CONFIGURE, new_data)
|
||||
|
||||
def request_done(self, request_id):
|
||||
@async_callback
|
||||
def async_request_done(self, request_id):
|
||||
"""Remove the configuration request."""
|
||||
if not self._validate_request_id(request_id):
|
||||
return
|
||||
@@ -167,15 +199,16 @@ class Configurator(object):
|
||||
# the result fo the service call (current design limitation).
|
||||
# Instead, we will set it to configured to give as feedback but delete
|
||||
# it shortly after so that it is deleted when the client updates.
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURED)
|
||||
self.hass.states.async_set(entity_id, STATE_CONFIGURED)
|
||||
|
||||
def deferred_remove(event):
|
||||
"""Remove the request state."""
|
||||
self.hass.states.remove(entity_id)
|
||||
self.hass.states.async_remove(entity_id)
|
||||
|
||||
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
|
||||
self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
|
||||
|
||||
def handle_service_call(self, call):
|
||||
@async_callback
|
||||
def async_handle_service_call(self, call):
|
||||
"""Handle a configure service call."""
|
||||
request_id = call.data.get(ATTR_CONFIGURE_ID)
|
||||
|
||||
@@ -186,8 +219,8 @@ class Configurator(object):
|
||||
entity_id, fields, callback = self._requests[request_id]
|
||||
|
||||
# field validation goes here?
|
||||
|
||||
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
|
||||
if callback:
|
||||
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
|
||||
|
||||
def _generate_unique_id(self):
|
||||
"""Generate a unique configurator ID."""
|
||||
|
||||
@@ -18,10 +18,10 @@ from homeassistant.const import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_AVAILABLE = "available"
|
||||
ATTR_SENSOR_STRENGTH = "sensor reflection rate"
|
||||
ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)"
|
||||
ATTR_TIME_IN_STATE = "time in state"
|
||||
ATTR_AVAILABLE = 'available'
|
||||
ATTR_SENSOR_STRENGTH = 'sensor_reflection_rate'
|
||||
ATTR_SIGNAL_STRENGTH = 'wifi_signal_strength'
|
||||
ATTR_TIME_IN_STATE = 'time_in_state'
|
||||
|
||||
DEFAULT_NAME = 'Garadget'
|
||||
|
||||
|
||||
@@ -8,11 +8,10 @@ import logging
|
||||
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE)
|
||||
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION)
|
||||
from homeassistant.components.lutron_caseta import (
|
||||
LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['lutron_caseta']
|
||||
@@ -38,13 +37,18 @@ class LutronCasetaCover(LutronCasetaDevice, CoverDevice):
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
return self._state["current_state"] < 1
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of cover."""
|
||||
return self._state["current_state"]
|
||||
|
||||
def close_cover(self):
|
||||
"""Close the cover."""
|
||||
self._smartbridge.set_value(self._device_id, 0)
|
||||
|
||||
@@ -4,42 +4,18 @@ Support for MySensors covers.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.mysensors/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
|
||||
from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = []
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the MySensors platform for covers."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
|
||||
if not gateways:
|
||||
return
|
||||
|
||||
for gateway in gateways:
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
|
||||
}
|
||||
if float(gateway.protocol_version) >= 1.5:
|
||||
map_sv_types.update({
|
||||
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
|
||||
})
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
map_sv_types, devices, MySensorsCover, add_devices))
|
||||
"""Setup the mysensors platform for covers."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
|
||||
class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
||||
"""Representation of the value of a MySensors Cover child node."""
|
||||
|
||||
@property
|
||||
|
||||
@@ -24,9 +24,13 @@ CONF_RELAY_PIN = 'relay_pin'
|
||||
CONF_RELAY_TIME = 'relay_time'
|
||||
CONF_STATE_PIN = 'state_pin'
|
||||
CONF_STATE_PULL_MODE = 'state_pull_mode'
|
||||
CONF_INVERT_STATE = 'invert_state'
|
||||
CONF_INVERT_RELAY = 'invert_relay'
|
||||
|
||||
DEFAULT_RELAY_TIME = .2
|
||||
DEFAULT_STATE_PULL_MODE = 'UP'
|
||||
DEFAULT_INVERT_STATE = False
|
||||
DEFAULT_INVERT_RELAY = False
|
||||
DEPENDENCIES = ['rpi_gpio']
|
||||
|
||||
_COVERS_SCHEMA = vol.All(
|
||||
@@ -45,6 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE):
|
||||
cv.string,
|
||||
vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int,
|
||||
vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean,
|
||||
vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
@@ -53,13 +59,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the RPi cover platform."""
|
||||
relay_time = config.get(CONF_RELAY_TIME)
|
||||
state_pull_mode = config.get(CONF_STATE_PULL_MODE)
|
||||
invert_state = config.get(CONF_INVERT_STATE)
|
||||
invert_relay = config.get(CONF_INVERT_RELAY)
|
||||
covers = []
|
||||
covers_conf = config.get(CONF_COVERS)
|
||||
|
||||
for cover in covers_conf:
|
||||
covers.append(RPiGPIOCover(
|
||||
cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN],
|
||||
state_pull_mode, relay_time))
|
||||
state_pull_mode, relay_time, invert_state, invert_relay))
|
||||
add_devices(covers)
|
||||
|
||||
|
||||
@@ -67,7 +75,7 @@ class RPiGPIOCover(CoverDevice):
|
||||
"""Representation of a Raspberry GPIO cover."""
|
||||
|
||||
def __init__(self, name, relay_pin, state_pin, state_pull_mode,
|
||||
relay_time):
|
||||
relay_time, invert_state, invert_relay):
|
||||
"""Initialize the cover."""
|
||||
self._name = name
|
||||
self._state = False
|
||||
@@ -75,9 +83,11 @@ class RPiGPIOCover(CoverDevice):
|
||||
self._state_pin = state_pin
|
||||
self._state_pull_mode = state_pull_mode
|
||||
self._relay_time = relay_time
|
||||
self._invert_state = invert_state
|
||||
self._invert_relay = invert_relay
|
||||
rpi_gpio.setup_output(self._relay_pin)
|
||||
rpi_gpio.setup_input(self._state_pin, self._state_pull_mode)
|
||||
rpi_gpio.write_output(self._relay_pin, True)
|
||||
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
@@ -96,13 +106,13 @@ class RPiGPIOCover(CoverDevice):
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed."""
|
||||
return self._state
|
||||
return self._state != self._invert_state
|
||||
|
||||
def _trigger(self):
|
||||
"""Trigger the cover."""
|
||||
rpi_gpio.write_output(self._relay_pin, False)
|
||||
rpi_gpio.write_output(self._relay_pin, self._invert_relay)
|
||||
sleep(self._relay_time)
|
||||
rpi_gpio.write_output(self._relay_pin, True)
|
||||
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
|
||||
|
||||
def close_cover(self):
|
||||
"""Close the cover."""
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
|
||||
EVENT_HOMEASSISTANT_START, MATCH_ALL,
|
||||
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
|
||||
STATE_OPEN, STATE_CLOSED)
|
||||
CONF_OPTIMISTIC, STATE_OPEN, STATE_CLOSED)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
@@ -39,6 +39,8 @@ CLOSE_ACTION = 'close_cover'
|
||||
STOP_ACTION = 'stop_cover'
|
||||
POSITION_ACTION = 'set_cover_position'
|
||||
TILT_ACTION = 'set_cover_tilt_position'
|
||||
CONF_TILT_OPTIMISTIC = 'tilt_optimistic'
|
||||
|
||||
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
|
||||
CONF_OPEN_OR_CLOSE = 'open_or_close'
|
||||
|
||||
@@ -56,6 +58,8 @@ COVER_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_FRIENDLY_NAME, default=None): cv.string,
|
||||
@@ -83,11 +87,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
stop_action = device_config.get(STOP_ACTION)
|
||||
position_action = device_config.get(POSITION_ACTION)
|
||||
tilt_action = device_config.get(TILT_ACTION)
|
||||
|
||||
if position_template is None and state_template is None:
|
||||
_LOGGER.error('Must specify either %s' or '%s',
|
||||
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
|
||||
continue
|
||||
optimistic = device_config.get(CONF_OPTIMISTIC)
|
||||
tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC)
|
||||
|
||||
if position_action is None and open_action is None:
|
||||
_LOGGER.error('Must specify at least one of %s' or '%s',
|
||||
@@ -125,7 +126,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
device, friendly_name, state_template,
|
||||
position_template, tilt_template, icon_template,
|
||||
open_action, close_action, stop_action,
|
||||
position_action, tilt_action, entity_ids
|
||||
position_action, tilt_action,
|
||||
optimistic, tilt_optimistic, entity_ids
|
||||
)
|
||||
)
|
||||
if not covers:
|
||||
@@ -142,7 +144,8 @@ class CoverTemplate(CoverDevice):
|
||||
def __init__(self, hass, device_id, friendly_name, state_template,
|
||||
position_template, tilt_template, icon_template,
|
||||
open_action, close_action, stop_action,
|
||||
position_action, tilt_action, entity_ids):
|
||||
position_action, tilt_action,
|
||||
optimistic, tilt_optimistic, entity_ids):
|
||||
"""Initialize the Template cover."""
|
||||
self.hass = hass
|
||||
self.entity_id = async_generate_entity_id(
|
||||
@@ -167,6 +170,9 @@ class CoverTemplate(CoverDevice):
|
||||
self._tilt_script = None
|
||||
if tilt_action is not None:
|
||||
self._tilt_script = Script(hass, tilt_action)
|
||||
self._optimistic = (optimistic or
|
||||
(not state_template and not position_template))
|
||||
self._tilt_optimistic = tilt_optimistic or not tilt_template
|
||||
self._icon = None
|
||||
self._position = None
|
||||
self._tilt_value = None
|
||||
@@ -260,19 +266,23 @@ class CoverTemplate(CoverDevice):
|
||||
def async_open_cover(self, **kwargs):
|
||||
"""Move the cover up."""
|
||||
if self._open_script:
|
||||
self.hass.async_add_job(self._open_script.async_run())
|
||||
yield from self._open_script.async_run()
|
||||
elif self._position_script:
|
||||
self.hass.async_add_job(self._position_script.async_run(
|
||||
{"position": 100}))
|
||||
yield from self._position_script.async_run({"position": 100})
|
||||
if self._optimistic:
|
||||
self._position = 100
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_close_cover(self, **kwargs):
|
||||
"""Move the cover down."""
|
||||
if self._close_script:
|
||||
self.hass.async_add_job(self._close_script.async_run())
|
||||
yield from self._close_script.async_run()
|
||||
elif self._position_script:
|
||||
self.hass.async_add_job(self._position_script.async_run(
|
||||
{"position": 0}))
|
||||
yield from self._position_script.async_run({"position": 0})
|
||||
if self._optimistic:
|
||||
self._position = 0
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_stop_cover(self, **kwargs):
|
||||
@@ -284,29 +294,35 @@ class CoverTemplate(CoverDevice):
|
||||
def async_set_cover_position(self, **kwargs):
|
||||
"""Set cover position."""
|
||||
self._position = kwargs[ATTR_POSITION]
|
||||
self.hass.async_add_job(self._position_script.async_run(
|
||||
{"position": self._position}))
|
||||
yield from self._position_script.async_run(
|
||||
{"position": self._position})
|
||||
if self._optimistic:
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_open_cover_tilt(self, **kwargs):
|
||||
"""Tilt the cover open."""
|
||||
self._tilt_value = 100
|
||||
self.hass.async_add_job(self._tilt_script.async_run(
|
||||
{"tilt": self._tilt_value}))
|
||||
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
|
||||
if self._tilt_optimistic:
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_close_cover_tilt(self, **kwargs):
|
||||
"""Tilt the cover closed."""
|
||||
self._tilt_value = 0
|
||||
self.hass.async_add_job(self._tilt_script.async_run(
|
||||
{"tilt": self._tilt_value}))
|
||||
yield from self._tilt_script.async_run(
|
||||
{"tilt": self._tilt_value})
|
||||
if self._tilt_optimistic:
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_set_cover_tilt_position(self, **kwargs):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
self._tilt_value = kwargs[ATTR_TILT_POSITION]
|
||||
self.hass.async_add_job(self._tilt_script.async_run(
|
||||
{"tilt": self._tilt_value}))
|
||||
yield from self._tilt_script.async_run({"tilt": self._tilt_value})
|
||||
if self._tilt_optimistic:
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
|
||||
@@ -31,6 +31,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
|
||||
'sensor',
|
||||
'switch',
|
||||
'tts',
|
||||
'mailbox',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -6,28 +6,32 @@ https://home-assistant.io/components/device_tracker.automatic/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_tracker import (
|
||||
PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC,
|
||||
ATTR_GPS, ATTR_GPS_ACCURACY)
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_HOMEASSISTANT_START)
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
REQUIREMENTS = ['aioautomatic==0.4.0']
|
||||
REQUIREMENTS = ['aioautomatic==0.6.0']
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CLIENT_ID = 'client_id'
|
||||
CONF_SECRET = 'secret'
|
||||
CONF_DEVICES = 'devices'
|
||||
CONF_CURRENT_LOCATION = 'current_location'
|
||||
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
@@ -38,38 +42,74 @@ ATTR_FUEL_LEVEL = 'fuel_level'
|
||||
|
||||
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
|
||||
|
||||
AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json'
|
||||
|
||||
DATA_CONFIGURING = 'automatic_configurator_clients'
|
||||
DATA_REFRESH_TOKEN = 'refresh_token'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_SECRET): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
|
||||
vol.Optional(CONF_DEVICES, default=None): vol.All(
|
||||
cv.ensure_list, [cv.string])
|
||||
})
|
||||
|
||||
|
||||
def _get_refresh_token_from_file(hass, filename):
|
||||
"""Attempt to load session data from file."""
|
||||
path = hass.config.path(filename)
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(path) as data_file:
|
||||
data = json.load(data_file)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
return data.get(DATA_REFRESH_TOKEN)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _write_refresh_token_to_file(hass, filename, refresh_token):
|
||||
"""Attempt to store session data to file."""
|
||||
path = hass.config.path(filename)
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, 'w+') as data_file:
|
||||
json.dump({
|
||||
DATA_REFRESH_TOKEN: refresh_token
|
||||
}, data_file)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
"""Validate the configuration and return an Automatic scanner."""
|
||||
import aioautomatic
|
||||
|
||||
hass.http.register_view(AutomaticAuthCallbackView())
|
||||
|
||||
scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE
|
||||
|
||||
client = aioautomatic.Client(
|
||||
client_id=config[CONF_CLIENT_ID],
|
||||
client_secret=config[CONF_SECRET],
|
||||
client_session=async_get_clientsession(hass),
|
||||
request_kwargs={'timeout': DEFAULT_TIMEOUT})
|
||||
try:
|
||||
try:
|
||||
session = yield from client.create_session_from_password(
|
||||
FULL_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
|
||||
except aioautomatic.exceptions.ForbiddenError as exc:
|
||||
if not str(exc).startswith("invalid_scope"):
|
||||
raise exc
|
||||
_LOGGER.info("Client not authorized for current_location scope. "
|
||||
"location:updated events will not be received.")
|
||||
session = yield from client.create_session_from_password(
|
||||
DEFAULT_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
|
||||
|
||||
filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID])
|
||||
refresh_token = yield from hass.async_add_job(
|
||||
_get_refresh_token_from_file, hass, filename)
|
||||
|
||||
@asyncio.coroutine
|
||||
def initialize_data(session):
|
||||
"""Initialize the AutomaticData object from the created session."""
|
||||
hass.async_add_job(
|
||||
_write_refresh_token_to_file, hass, filename,
|
||||
session.refresh_token)
|
||||
data = AutomaticData(
|
||||
hass, client, session, config[CONF_DEVICES], async_see)
|
||||
|
||||
@@ -77,26 +117,86 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
vehicles = yield from session.get_vehicles()
|
||||
for vehicle in vehicles:
|
||||
hass.async_add_job(data.load_vehicle(vehicle))
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
_LOGGER.error(str(err))
|
||||
return False
|
||||
|
||||
@callback
|
||||
def ws_connect(event):
|
||||
"""Open the websocket connection."""
|
||||
hass.async_add_job(data.ws_connect())
|
||||
# Create a task instead of adding a tracking job, since this task will
|
||||
# run until the websocket connection is closed.
|
||||
hass.loop.create_task(data.ws_connect())
|
||||
|
||||
@callback
|
||||
def ws_close(event):
|
||||
"""Close the websocket connection."""
|
||||
hass.async_add_job(data.ws_close())
|
||||
if refresh_token is not None:
|
||||
try:
|
||||
session = yield from client.create_session_from_refresh_token(
|
||||
refresh_token)
|
||||
yield from initialize_data(session)
|
||||
return True
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
_LOGGER.error(str(err))
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect)
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close)
|
||||
configurator = hass.components.configurator
|
||||
request_id = configurator.async_request_config(
|
||||
"Automatic", description=(
|
||||
"Authorization required for Automatic device tracker."),
|
||||
link_name="Click here to authorize Home Assistant.",
|
||||
link_url=client.generate_oauth_url(scope),
|
||||
entity_picture="/static/images/logo_automatic.png",
|
||||
)
|
||||
|
||||
@asyncio.coroutine
|
||||
def initialize_callback(code, state):
|
||||
"""Callback after OAuth2 response is returned."""
|
||||
try:
|
||||
session = yield from client.create_session_from_oauth_code(
|
||||
code, state)
|
||||
yield from initialize_data(session)
|
||||
configurator.async_request_done(request_id)
|
||||
except aioautomatic.exceptions.AutomaticError as err:
|
||||
_LOGGER.error(str(err))
|
||||
configurator.async_notify_errors(request_id, str(err))
|
||||
return False
|
||||
|
||||
if DATA_CONFIGURING not in hass.data:
|
||||
hass.data[DATA_CONFIGURING] = {}
|
||||
|
||||
hass.data[DATA_CONFIGURING][client.state] = initialize_callback
|
||||
return True
|
||||
|
||||
|
||||
class AutomaticAuthCallbackView(HomeAssistantView):
|
||||
"""Handle OAuth finish callback requests."""
|
||||
|
||||
requires_auth = False
|
||||
url = '/api/automatic/callback'
|
||||
name = 'api:automatic:callback'
|
||||
|
||||
@callback
|
||||
def get(self, request): # pylint: disable=no-self-use
|
||||
"""Finish OAuth callback request."""
|
||||
hass = request.app['hass']
|
||||
params = request.query
|
||||
response = web.HTTPFound('/states')
|
||||
|
||||
if 'state' not in params or 'code' not in params:
|
||||
if 'error' in params:
|
||||
_LOGGER.error(
|
||||
"Error authorizing Automatic: %s", params['error'])
|
||||
return response
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Error authorizing Automatic. Invalid response returned.")
|
||||
return response
|
||||
|
||||
if DATA_CONFIGURING not in hass.data or \
|
||||
params['state'] not in hass.data[DATA_CONFIGURING]:
|
||||
_LOGGER.error("Automatic configuration request not found.")
|
||||
return response
|
||||
|
||||
code = params['code']
|
||||
state = params['state']
|
||||
initialize_callback = hass.data[DATA_CONFIGURING][state]
|
||||
hass.async_add_job(initialize_callback(code, state))
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class AutomaticData(object):
|
||||
"""A class representing an Automatic cloud service connection."""
|
||||
|
||||
@@ -115,6 +215,8 @@ class AutomaticData(object):
|
||||
lambda name, event: self.hass.async_add_job(
|
||||
self.handle_event(name, event)))
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_event(self, name, event):
|
||||
"""Coroutine to update state for a realtime event."""
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Support for HUAWEI routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.huawei/
|
||||
"""
|
||||
import base64
|
||||
import logging
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.device_tracker import (
|
||||
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get_scanner(hass, config):
|
||||
"""Validate the configuration and return a HUAWEI scanner."""
|
||||
scanner = HuaweiDeviceScanner(config[DOMAIN])
|
||||
|
||||
return scanner
|
||||
|
||||
|
||||
Device = namedtuple('Device', ['name', 'ip', 'mac', 'state'])
|
||||
|
||||
|
||||
class HuaweiDeviceScanner(DeviceScanner):
|
||||
"""This class queries a router running HUAWEI firmware."""
|
||||
|
||||
ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);')
|
||||
DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),')
|
||||
DEVICE_ATTR_REGEX = re.compile(
|
||||
'"(?P<Domain>.*?)","(?P<IpAddr>.*?)",'
|
||||
'"(?P<MacAddr>.*?)","(?P<Port>.*?)",'
|
||||
'"(?P<IpType>.*?)","(?P<DevType>.*?)",'
|
||||
'"(?P<DevStatus>.*?)","(?P<PortType>.*?)",'
|
||||
'"(?P<Time>.*?)","(?P<HostName>.*?)",'
|
||||
'"(?P<IPv4Enabled>.*?)","(?P<IPv6Enabled>.*?)",'
|
||||
'"(?P<DeviceType>.*?)"')
|
||||
LOGIN_COOKIE = dict(Cookie='body:Language:portuguese:id=-1')
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = base64.b64encode(bytes(config[CONF_PASSWORD], 'utf-8'))
|
||||
|
||||
self.last_results = []
|
||||
|
||||
def scan_devices(self):
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return [client.mac for client in self.last_results]
|
||||
|
||||
def get_device_name(self, device):
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if not self.last_results:
|
||||
return None
|
||||
for client in self.last_results:
|
||||
if client.mac == device:
|
||||
return client.name
|
||||
return None
|
||||
|
||||
def _update_info(self):
|
||||
"""Ensure the information from the router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
data = self._get_data()
|
||||
if not data:
|
||||
return False
|
||||
|
||||
active_clients = [client for client in data if client.state]
|
||||
self.last_results = active_clients
|
||||
|
||||
_LOGGER.debug("Active clients: " + "\n"
|
||||
.join((client.mac + " " + client.name)
|
||||
for client in active_clients))
|
||||
return True
|
||||
|
||||
def _get_data(self):
|
||||
"""Get the devices' data from the router.
|
||||
|
||||
Returns a list with all the devices known to the router DHCP server.
|
||||
"""
|
||||
array_regex_res = self.ARRAY_REGEX.search(self._get_devices_response())
|
||||
|
||||
devices = []
|
||||
if array_regex_res:
|
||||
device_regex_res = self.DEVICE_REGEX.findall(
|
||||
array_regex_res.group(1))
|
||||
|
||||
for device in device_regex_res:
|
||||
device_attrs_regex_res = self.DEVICE_ATTR_REGEX.search(device)
|
||||
|
||||
devices.append(Device(device_attrs_regex_res.group('HostName'),
|
||||
device_attrs_regex_res.group('IpAddr'),
|
||||
device_attrs_regex_res.group('MacAddr'),
|
||||
device_attrs_regex_res.group(
|
||||
'DevStatus') == "Online"))
|
||||
|
||||
return devices
|
||||
|
||||
def _get_devices_response(self):
|
||||
"""Get the raw string with the devices from the router."""
|
||||
cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host))
|
||||
cnt_str = str(cnt.content, cnt.apparent_encoding, errors='replace')
|
||||
|
||||
_LOGGER.debug("Loggin in")
|
||||
cookie = requests.post('http://{}/login.cgi'.format(self.host),
|
||||
data=[('UserName', self.username),
|
||||
('PassWord', self.password),
|
||||
('x.X_HW_Token', cnt_str)],
|
||||
cookies=self.LOGIN_COOKIE)
|
||||
|
||||
_LOGGER.debug("Requesting lan user info update")
|
||||
# this request is needed or else some devices' state won't be updated
|
||||
requests.get(
|
||||
'http://{}/html/bbsp/common/lanuserinfo.asp'.format(self.host),
|
||||
cookies=cookie.cookies)
|
||||
|
||||
_LOGGER.debug("Requesting lan user info data")
|
||||
devices = requests.get(
|
||||
'http://{}/html/bbsp/common/GetLanUserDevInfo.asp'.format(
|
||||
self.host),
|
||||
cookies=cookie.cookies)
|
||||
|
||||
# we need to decode() using the request encoding, then encode() and
|
||||
# decode('unicode_escape') to replace \\xXX with \xXX
|
||||
# (i.e. \\x2d -> \x2d)
|
||||
return devices.content.decode(devices.apparent_encoding).encode().\
|
||||
decode('unicode_escape')
|
||||
@@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.location import distance
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -209,7 +208,7 @@ class Icloud(DeviceScanner):
|
||||
|
||||
if self.accountname in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(self.accountname)
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
# Trigger the next step immediately
|
||||
@@ -217,7 +216,7 @@ class Icloud(DeviceScanner):
|
||||
|
||||
def icloud_need_trusted_device(self):
|
||||
"""We need a trusted device."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
if self.accountname in _CONFIGURING:
|
||||
return
|
||||
|
||||
@@ -229,7 +228,7 @@ class Icloud(DeviceScanner):
|
||||
devicesstring += "{}: {};".format(i, devicename)
|
||||
|
||||
_CONFIGURING[self.accountname] = configurator.request_config(
|
||||
self.hass, 'iCloud {}'.format(self.accountname),
|
||||
'iCloud {}'.format(self.accountname),
|
||||
self.icloud_trusted_device_callback,
|
||||
description=(
|
||||
'Please choose your trusted device by entering'
|
||||
@@ -259,17 +258,17 @@ class Icloud(DeviceScanner):
|
||||
|
||||
if self.accountname in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(self.accountname)
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
|
||||
def icloud_need_verification_code(self):
|
||||
"""Return the verification code."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = self.hass.components.configurator
|
||||
if self.accountname in _CONFIGURING:
|
||||
return
|
||||
|
||||
_CONFIGURING[self.accountname] = configurator.request_config(
|
||||
self.hass, 'iCloud {}'.format(self.accountname),
|
||||
'iCloud {}'.format(self.accountname),
|
||||
self.icloud_verification_callback,
|
||||
description=('Please enter the validation code:'),
|
||||
entity_picture="/static/images/config_icloud.png",
|
||||
|
||||
@@ -4,61 +4,51 @@ Support for tracking MySensors devices.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.mysensors/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
from homeassistant.helpers.dispatcher import dispatcher_connect
|
||||
from homeassistant.util import slugify
|
||||
|
||||
DEPENDENCIES = ['mysensors']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see, discovery_info=None):
|
||||
"""Set up the MySensors tracker."""
|
||||
def mysensors_callback(gateway, msg):
|
||||
"""Set up callback for mysensors platform."""
|
||||
node = gateway.sensors[msg.node_id]
|
||||
if node.sketch_name is None:
|
||||
_LOGGER.debug("No sketch_name: node %s", msg.node_id)
|
||||
return
|
||||
"""Set up the MySensors device scanner."""
|
||||
new_devices = mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsDeviceScanner,
|
||||
device_args=(see, ))
|
||||
if not new_devices:
|
||||
return False
|
||||
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
|
||||
child = node.children.get(msg.child_id)
|
||||
if child is None:
|
||||
return
|
||||
position = child.values.get(set_req.V_POSITION)
|
||||
if child.type != pres.S_GPS or position is None:
|
||||
return
|
||||
try:
|
||||
latitude, longitude, _ = position.split(',')
|
||||
except ValueError:
|
||||
_LOGGER.error("Payload for V_POSITION %s is not of format "
|
||||
"latitude, longitude, altitude", position)
|
||||
return
|
||||
name = '{} {} {}'.format(
|
||||
node.sketch_name, msg.node_id, child.id)
|
||||
attr = {
|
||||
mysensors.ATTR_CHILD_ID: child.id,
|
||||
mysensors.ATTR_DESCRIPTION: child.description,
|
||||
mysensors.ATTR_DEVICE: gateway.device,
|
||||
mysensors.ATTR_NODE_ID: msg.node_id,
|
||||
}
|
||||
see(
|
||||
dev_id=slugify(name),
|
||||
host_name=name,
|
||||
gps=(latitude, longitude),
|
||||
battery=node.battery_level,
|
||||
attributes=attr
|
||||
)
|
||||
|
||||
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
|
||||
|
||||
for gateway in gateways:
|
||||
if float(gateway.protocol_version) < 2.0:
|
||||
continue
|
||||
gateway.platform_callbacks.append(mysensors_callback)
|
||||
for device in new_devices:
|
||||
dev_id = (
|
||||
id(device.gateway), device.node_id, device.child_id,
|
||||
device.value_type)
|
||||
dispatcher_connect(
|
||||
hass, mysensors.SIGNAL_CALLBACK.format(*dev_id),
|
||||
device.update_callback)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MySensorsDeviceScanner(mysensors.MySensorsDevice):
|
||||
"""Represent a MySensors scanner."""
|
||||
|
||||
def __init__(self, see, *args):
|
||||
"""Set up instance."""
|
||||
super().__init__(*args)
|
||||
self.see = see
|
||||
|
||||
def update_callback(self):
|
||||
"""Update the device."""
|
||||
self.update()
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
child = node.children[self.child_id]
|
||||
position = child.values[self.value_type]
|
||||
latitude, longitude, _ = position.split(',')
|
||||
|
||||
self.see(
|
||||
dev_id=slugify(self.name),
|
||||
host_name=self.name,
|
||||
gps=(latitude, longitude),
|
||||
battery=node.battery_level,
|
||||
attributes=self.device_state_attributes
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ SERVICE_IKEA_TRADFRI = 'ikea_tradfri'
|
||||
SERVICE_HASSIO = 'hassio'
|
||||
SERVICE_AXIS = 'axis'
|
||||
SERVICE_APPLE_TV = 'apple_tv'
|
||||
SERVICE_WINK = 'wink'
|
||||
|
||||
SERVICE_HANDLERS = {
|
||||
SERVICE_HASS_IOS_APP: ('ios', None),
|
||||
@@ -42,6 +43,7 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_HASSIO: ('hassio', None),
|
||||
SERVICE_AXIS: ('axis', None),
|
||||
SERVICE_APPLE_TV: ('apple_tv', None),
|
||||
SERVICE_WINK: ('wink', None),
|
||||
'philips_hue': ('light', 'hue'),
|
||||
'google_cast': ('media_player', 'cast'),
|
||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||
@@ -57,7 +59,9 @@ SERVICE_HANDLERS = {
|
||||
'frontier_silicon': ('media_player', 'frontier_silicon'),
|
||||
'openhome': ('media_player', 'openhome'),
|
||||
'harmony': ('remote', 'harmony'),
|
||||
'sabnzbd': ('sensor', 'sabnzbd'),
|
||||
'bose_soundtouch': ('media_player', 'soundtouch'),
|
||||
'bluesound': ('media_player', 'bluesound'),
|
||||
}
|
||||
|
||||
CONF_IGNORE = 'ignore'
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.helpers import discovery
|
||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
|
||||
CONF_DEVICES
|
||||
|
||||
REQUIREMENTS = ['libpurecoollink==0.2.0']
|
||||
REQUIREMENTS = ['libpurecoollink==0.4.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -69,14 +69,17 @@ def setup(hass, config):
|
||||
dyson_device = next((d for d in dyson_devices if
|
||||
d.serial == device["device_id"]), None)
|
||||
if dyson_device:
|
||||
connected = dyson_device.connect(None, device["device_ip"],
|
||||
timeout, retry)
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", dyson_device)
|
||||
hass.data[DYSON_DEVICES].append(dyson_device)
|
||||
else:
|
||||
_LOGGER.warning("Unable to connect to device %s",
|
||||
dyson_device)
|
||||
try:
|
||||
connected = dyson_device.connect(device["device_ip"])
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", dyson_device)
|
||||
hass.data[DYSON_DEVICES].append(dyson_device)
|
||||
else:
|
||||
_LOGGER.warning("Unable to connect to device %s",
|
||||
dyson_device)
|
||||
except OSError as ose:
|
||||
_LOGGER.error("Unable to connect to device %s: %s",
|
||||
str(dyson_device.network_device), str(ose))
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Unable to find device %s in Dyson account",
|
||||
@@ -86,7 +89,7 @@ def setup(hass, config):
|
||||
for device in dyson_devices:
|
||||
_LOGGER.info("Trying to connect to device %s with timeout=%i "
|
||||
"and retry=%i", device, timeout, retry)
|
||||
connected = device.connect(None, None, timeout, retry)
|
||||
connected = device.auto_connect(timeout, retry)
|
||||
if connected:
|
||||
_LOGGER.info("Connected to device %s", device)
|
||||
hass.data[DYSON_DEVICES].append(device)
|
||||
@@ -98,5 +101,6 @@ def setup(hass, config):
|
||||
_LOGGER.debug("Starting sensor/fan components")
|
||||
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, "fan", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
@@ -13,10 +13,9 @@ import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['python-ecobee-api==0.0.7']
|
||||
REQUIREMENTS = ['python-ecobee-api==0.0.8']
|
||||
|
||||
_CONFIGURING = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
|
||||
def request_configuration(network, hass, config):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
configurator.notify_errors(
|
||||
_CONFIGURING['ecobee'], "Failed to register, please try again.")
|
||||
@@ -56,7 +55,7 @@ def request_configuration(network, hass, config):
|
||||
setup_ecobee(hass, network, config)
|
||||
|
||||
_CONFIGURING['ecobee'] = configurator.request_config(
|
||||
hass, "Ecobee", ecobee_configuration_callback,
|
||||
"Ecobee", ecobee_configuration_callback,
|
||||
description=(
|
||||
'Please authorize this app at https://www.ecobee.com/consumer'
|
||||
'portal/index.html with pin code: ' + network.pin),
|
||||
@@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config):
|
||||
return
|
||||
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(_CONFIGURING.pop('ecobee'))
|
||||
|
||||
hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)
|
||||
|
||||
@@ -193,7 +193,9 @@ class Config(object):
|
||||
if entity_id == ent_id:
|
||||
return number
|
||||
|
||||
number = str(max(int(k) for k in self.numbers) + 1)
|
||||
number = '1'
|
||||
if self.numbers:
|
||||
number = str(max(int(k) for k in self.numbers) + 1)
|
||||
self.numbers[number] = entity_id
|
||||
self._save_numbers_json()
|
||||
return number
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import threading
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
import select
|
||||
|
||||
from aiohttp import web
|
||||
@@ -86,18 +85,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
advertise_ip, advertise_port).replace("\n", "\r\n") \
|
||||
.encode('utf-8')
|
||||
|
||||
# Set up a pipe for signaling to the receiver that it's time to
|
||||
# shutdown. Essentially, we place the SSDP socket into nonblocking
|
||||
# mode and use select() to wait for data to arrive on either the SSDP
|
||||
# socket or the pipe. If data arrives on either one, select() returns
|
||||
# and tells us which filenos have data ready to read.
|
||||
#
|
||||
# When we want to stop the responder, we write data to the pipe, which
|
||||
# causes the select() to return and indicate that said pipe has data
|
||||
# ready to be read, which indicates to us that the responder needs to
|
||||
# be shutdown.
|
||||
self._interrupted_read_pipe, self._interrupted_write_pipe = os.pipe()
|
||||
|
||||
def run(self):
|
||||
"""Run the server."""
|
||||
# Listen for UDP port 1900 packets sent to SSDP multicast address
|
||||
@@ -119,7 +106,7 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
socket.inet_aton(self.host_ip_addr))
|
||||
|
||||
if self.upnp_bind_multicast:
|
||||
ssdp_socket.bind(("239.255.255.250", 1900))
|
||||
ssdp_socket.bind(("", 1900))
|
||||
else:
|
||||
ssdp_socket.bind((self.host_ip_addr, 1900))
|
||||
|
||||
@@ -130,16 +117,13 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
|
||||
try:
|
||||
read, _, _ = select.select(
|
||||
[self._interrupted_read_pipe, ssdp_socket], [],
|
||||
[ssdp_socket])
|
||||
[ssdp_socket], [],
|
||||
[ssdp_socket], 2)
|
||||
|
||||
if self._interrupted_read_pipe in read:
|
||||
# Implies self._interrupted is True
|
||||
clean_socket_close(ssdp_socket)
|
||||
return
|
||||
elif ssdp_socket in read:
|
||||
if ssdp_socket in read:
|
||||
data, addr = ssdp_socket.recvfrom(1024)
|
||||
else:
|
||||
# most likely the timeout, so check for interupt
|
||||
continue
|
||||
except socket.error as ex:
|
||||
if self._interrupted:
|
||||
@@ -148,6 +132,9 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
|
||||
_LOGGER.error("UPNP Responder socket exception occured: %s",
|
||||
ex.__str__)
|
||||
# without the following continue, a second exception occurs
|
||||
# because the data object has not been initialized
|
||||
continue
|
||||
|
||||
if "M-SEARCH" in data.decode('utf-8'):
|
||||
# SSDP M-SEARCH method received, respond to it with our info
|
||||
@@ -161,7 +148,6 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
"""Stop the server."""
|
||||
# Request for server
|
||||
self._interrupted = True
|
||||
os.write(self._interrupted_write_pipe, bytes([0]))
|
||||
self.join()
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
REQUIREMENTS = ['pyenvisalink==2.1']
|
||||
REQUIREMENTS = ['pyenvisalink==2.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -74,9 +74,9 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
|
||||
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=15)),
|
||||
vol.Optional(CONF_ZONEDUMP_INTERVAL,
|
||||
default=DEFAULT_ZONEDUMP_INTERVAL):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=15)),
|
||||
vol.Optional(
|
||||
CONF_ZONEDUMP_INTERVAL,
|
||||
default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int),
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
hass.data[DYSON_FAN_DEVICES] = []
|
||||
|
||||
# Get Dyson Devices from parent component
|
||||
for device in hass.data[DYSON_DEVICES]:
|
||||
from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
|
||||
for device in [d for d in hass.data[DYSON_DEVICES] if
|
||||
isinstance(d, DysonPureCoolLink)]:
|
||||
dyson_entity = DysonPureCoolLinkDevice(hass, device)
|
||||
hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
|
||||
|
||||
@@ -83,8 +85,8 @@ class DysonPureCoolLinkDevice(FanEntity):
|
||||
|
||||
def on_message(self, message):
|
||||
"""Called when new messages received from the fan."""
|
||||
from libpurecoollink.dyson import DysonState
|
||||
if isinstance(message, DysonState):
|
||||
from libpurecoollink.dyson_pure_state import DysonPureCoolState
|
||||
if isinstance(message, DysonPureCoolState):
|
||||
_LOGGER.debug("Message received for fan device %s : %s", self.name,
|
||||
message)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@@ -13,7 +13,6 @@ from homeassistant.components.fan import (
|
||||
ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
||||
SUPPORT_SET_SPEED, FanEntity)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.util as util
|
||||
|
||||
_CONFIGURING = {}
|
||||
@@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
def request_configuration(device_id, insteonhub, model, hass,
|
||||
add_devices_callback):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
|
||||
# We got an error if this method is called while we are configuring
|
||||
if device_id in _CONFIGURING:
|
||||
@@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass,
|
||||
add_devices_callback)
|
||||
|
||||
_CONFIGURING[device_id] = configurator.request_config(
|
||||
hass, 'Insteon ' + model + ' addr: ' + device_id,
|
||||
'Insteon ' + model + ' addr: ' + device_id,
|
||||
insteon_fan_config_callback,
|
||||
description=('Enter a name for ' + model + ' Fan addr: ' + device_id),
|
||||
entity_picture='/static/images/config_insteon.png',
|
||||
@@ -85,7 +84,7 @@ def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
|
||||
"""Set up the fan."""
|
||||
if device_id in _CONFIGURING:
|
||||
request_id = _CONFIGURING.pop(device_id)
|
||||
configurator = get_component('configurator')
|
||||
configurator = hass.components.configurator
|
||||
configurator.request_done(request_id)
|
||||
_LOGGER.info("Device configuration done!")
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Define term used for medium speed. This must be set as the fan component uses
|
||||
# 'medium' which the ISY does not understand
|
||||
ISY_SPEED_MEDIUM = 'med'
|
||||
|
||||
|
||||
VALUE_TO_STATE = {
|
||||
0: SPEED_OFF,
|
||||
63: SPEED_LOW,
|
||||
@@ -29,7 +34,7 @@ STATE_TO_VALUE = {}
|
||||
for key in VALUE_TO_STATE:
|
||||
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
|
||||
|
||||
STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH]
|
||||
STATES = [SPEED_OFF, SPEED_LOW, ISY_SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@@ -93,6 +98,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity):
|
||||
else:
|
||||
self.speed = self.state
|
||||
|
||||
@property
|
||||
def speed_list(self) -> list:
|
||||
"""Get the list of available speeds."""
|
||||
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
|
||||
class ISYFanProgram(ISYFanDevice):
|
||||
"""Representation of an ISY994 fan program."""
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import (
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['ha-ffmpeg==1.5']
|
||||
REQUIREMENTS = ['ha-ffmpeg==1.7']
|
||||
|
||||
DOMAIN = 'ffmpeg'
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ def setup(hass, config):
|
||||
register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location')
|
||||
|
||||
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
|
||||
'dev-template', 'kiosk'):
|
||||
'dev-template', 'dev-mqtt', 'kiosk'):
|
||||
register_built_in_panel(hass, panel)
|
||||
|
||||
themes = config.get(DOMAIN, {}).get(ATTR_THEMES)
|
||||
@@ -361,7 +361,8 @@ class IndexView(HomeAssistantView):
|
||||
core_url=core_url, ui_url=ui_url,
|
||||
compatibility_url=compatibility_url, no_auth=no_auth,
|
||||
icons_url=icons_url, icons=FINGERPRINTS['mdi.html'],
|
||||
panel_url=panel_url, panels=hass.data[DATA_PANELS])
|
||||
panel_url=panel_url, panels=hass.data[DATA_PANELS],
|
||||
dev_mode=request.app[KEY_DEVELOPMENT])
|
||||
|
||||
return web.Response(text=resp, content_type='text/html')
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
</script>
|
||||
<script src='{{ core_url }}'></script>
|
||||
{% if not dev_mode %}
|
||||
<script src='/static/custom-elements-es5-adapter.js'></script>
|
||||
{% endif %}
|
||||
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
|
||||
{% if panel_url -%}
|
||||
<link rel='import' href='{{ panel_url }}' onerror='initError()' async>
|
||||
@@ -103,7 +106,7 @@
|
||||
var e = document.createElement('script');
|
||||
e.async = true;
|
||||
e.onerror = initError;
|
||||
e.src = '/static/webcomponents-lite.min.js';
|
||||
e.src = '/static/webcomponents-lite.js';
|
||||
document.head.appendChild(e);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
|
||||
|
||||
FINGERPRINTS = {
|
||||
"compatibility.js": "8e4c44b5f4288cc48ec1ba94a9bec812",
|
||||
"core.js": "d4a7cb8c80c62b536764e0e81385f6aa",
|
||||
"frontend.html": "7d599996578579600f1000d6d25e649d",
|
||||
"compatibility.js": "1686167ff210e001f063f5c606b2e74b",
|
||||
"core.js": "2a7d01e45187c7d4635da05065b5e54e",
|
||||
"frontend.html": "6c8192a4393c9e83516dc8177b75c23d",
|
||||
"mdi.html": "e91f61a039ed0a9936e7ee5360da3870",
|
||||
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
|
||||
"panels/ha-panel-automation.html": "1982116c49ad26ee8d89295edc797084",
|
||||
"panels/ha-panel-config.html": "fafeac72f83dd6cc42218f8978f6a7af",
|
||||
"panels/ha-panel-dev-event.html": "77784d5f0c73fcc3b29b6cc050bdf324",
|
||||
"panels/ha-panel-dev-info.html": "24e888ec7a8acd0c395b34396e9001bc",
|
||||
"panels/ha-panel-dev-service.html": "86a42a17f4894478b6b77bc636beafd0",
|
||||
"panels/ha-panel-dev-state.html": "31ef6ffe3347cdda5bb0cbbc54b62cde",
|
||||
"panels/ha-panel-dev-template.html": "d1d76e20fe9622cddee33e67318abde8",
|
||||
"panels/ha-panel-hassio.html": "262d31efd9add719e0325da5cf79a096",
|
||||
"panels/ha-panel-history.html": "35177e2046c9a4191c8f51f8160255ce",
|
||||
"panels/ha-panel-iframe.html": "238189f21e670b6dcfac937e5ebd7d3b",
|
||||
"panels/ha-panel-kiosk.html": "2ac2df41bd447600692a0054892fc094",
|
||||
"panels/ha-panel-logbook.html": "7c45bd41c146ec38b9938b8a5188bb0d",
|
||||
"panels/ha-panel-map.html": "50501cd53eb4304e9e46eb719aa894b7",
|
||||
"panels/ha-panel-shopping-list.html": "1d7126efc9ff9a102df7465d803a11d1",
|
||||
"panels/ha-panel-zwave.html": "422f95f820f8b6b231265351ffcf4dd1"
|
||||
"panels/ha-panel-config.html": "bd20a3b11b46522e3c705a0b6a72b9dc",
|
||||
"panels/ha-panel-dev-event.html": "d409e7ab537d9fe629126d122345279c",
|
||||
"panels/ha-panel-dev-info.html": "b0e55eb657fd75f21aba2426ac0cedc0",
|
||||
"panels/ha-panel-dev-mqtt.html": "94b222b013a98583842de3e72d5888c6",
|
||||
"panels/ha-panel-dev-service.html": "422b2c181ee0713fa31d45a64e605baf",
|
||||
"panels/ha-panel-dev-state.html": "7948d3dba058f31517d880df8ed0e857",
|
||||
"panels/ha-panel-dev-template.html": "f47b6910d8e4880e22cc508ca452f9b6",
|
||||
"panels/ha-panel-hassio.html": "b46e7619f3c355f872d5370741d89f6a",
|
||||
"panels/ha-panel-history.html": "fe2daac10a14f51fa3eb7d23978df1f7",
|
||||
"panels/ha-panel-iframe.html": "56930204d6e067a3d600cf030f4b34c8",
|
||||
"panels/ha-panel-kiosk.html": "b40aa5cb52dd7675bea744afcf9eebf8",
|
||||
"panels/ha-panel-logbook.html": "771afdcf48dc7e308b0282417d2e02d8",
|
||||
"panels/ha-panel-mailbox.html": "a8cca44ca36553e91565e3c894ea6323",
|
||||
"panels/ha-panel-map.html": "c2544fff3eedb487d44105cf94b335ec",
|
||||
"panels/ha-panel-shopping-list.html": "d8cfd0ecdb3aa6214c0f6908c34c7141"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
!function(){"use strict";function e(e,t){if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var r=Object(e),n=1;n<arguments.length;n++){var o=arguments[n];if(void 0!==o&&null!==o)for(var i=Object.keys(Object(o)),l=0,c=i.length;l<c;l++){var a=i[l],b=Object.getOwnPropertyDescriptor(o,a);void 0!==b&&b.enumerable&&(r[a]=o[a])}}return r}function t(){Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:e})}({assign:e,polyfill:t}).polyfill()}();
|
||||
!function(){"use strict";function e(e,t){if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var r=Object(e),n=1;n<arguments.length;n++){var o=arguments[n];if(void 0!==o&&null!==o)for(var i=Object.keys(Object(o)),l=0,c=i.length;l<c;l++){var a=i[l],b=Object.getOwnPropertyDescriptor(o,a);void 0!==b&&b.enumerable&&(r[a]=o[a])}}return r}({assign:e,polyfill:function(){Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:e})}}).polyfill()}();
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
(()=>{'use strict';if(!window.customElements)return;const a=window.HTMLElement,b=window.customElements.define,c=window.customElements.get,d=new Map,e=new Map;let f=!1,g=!1;window.HTMLElement=function(){if(!f){const a=d.get(this.constructor),b=c.call(window.customElements,a);g=!0;const e=new b;return e}f=!1;},window.HTMLElement.prototype=a.prototype;Object.defineProperty(window,'customElements',{value:window.customElements,configurable:!0,writable:!0}),Object.defineProperty(window.customElements,'define',{value:(c,h)=>{const i=h.prototype,j=class extends a{constructor(){super(),Object.setPrototypeOf(this,i),g||(f=!0,h.call(this)),g=!1;}},k=j.prototype;j.observedAttributes=h.observedAttributes,k.connectedCallback=i.connectedCallback,k.disconnectedCallback=i.disconnectedCallback,k.attributeChangedCallback=i.attributeChangedCallback,k.adoptedCallback=i.adoptedCallback,d.set(h,c),e.set(c,h),b.call(window.customElements,c,j);},configurable:!0,writable:!0}),Object.defineProperty(window.customElements,'get',{value:(a)=>e.get(a),configurable:!0,writable:!0});})();
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
}());
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,631 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg,
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer {
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile {
|
||||
will-change: opacity;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
-o-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path {
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 19px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 4px 0 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +1,2 @@
|
||||
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:16px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header slot="header" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hass.config.core.version]]</p><p>Path to configuration.yaml: [[hass.config.core.config_dir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the Apache 2.0 license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a> — <a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},polymerVersion:{type:String,value:Polymer.version},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(e){e&&e.preventDefault(),this.errorLog="Loading error log…",this.hass.callApi("GET","error_log").then(function(e){this.errorLog=e||"No errors have been reported."}.bind(this))}})</script></body></html>
|
||||
<html><head></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial;}.content{padding:16px;}.about{text-align:center;line-height:2em;}.version{@apply (--paper-font-headline);}.develop{@apply (--paper-font-subhead);}.about a{color:var(--dark-primary-color);}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px;}paper-icon-button{float:right;}.error-log{@apply (--paper-font-code1)
|
||||
clear: both;white-space:pre-wrap;}</style><app-header-layout has-scrolling-region=""><app-header slot="header" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hass.config.core.version]]</p><p>Path to configuration.yaml: [[hass.config.core.config_dir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the Apache 2.0 license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a> — <a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},polymerVersion:{type:String,value:Polymer.version},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(e){e&&e.preventDefault(),this.errorLog="Loading error log…",this.hass.callApi("GET","error_log").then(function(e){this.errorLog=e||"No errors have been reported."}.bind(this))}});</script></body></html>
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
<html><head></head><body><dom-module id="ha-panel-dev-mqtt"><template><style include="ha-style">:host{-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial;}.content{padding:24px 0 32px;max-width:600px;margin:0 auto;}paper-card{display:block;}paper-button{background-color:white;}</style><app-header-layout has-scrolling-region=""><app-header slot="header" fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">MQTT</div></app-toolbar></app-header><app-localstorage-document key="panel-dev-mqtt-topic" data="{{topic}}"></app-localstorage-document><app-localstorage-document key="panel-dev-mqtt-payload" data="{{payload}}"></app-localstorage-document><div class="content"><paper-card heading="Publish a packet"><div class="card-content"><paper-input label="topic" value="{{topic}}"></paper-input><paper-textarea always-float-label="" label="Payload (template allowed)" value="{{payload}}"></paper-textarea></div><div class="card-actions"><paper-button on-tap="_publish">Publish</paper-button></div></paper-card></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-mqtt",properties:{hass:Object,narrow:Boolean,showMenu:Boolean,topic:String,payload:String},_publish:function(){this.hass.callService("mqtt","publish",{topic:this.topic,payload_template:this.payload})}});</script></body></html>
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user