mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
commit
c366fa00d8
40
.coveragerc
40
.coveragerc
@ -19,6 +19,9 @@ omit =
|
|||||||
homeassistant/components/alarmdecoder.py
|
homeassistant/components/alarmdecoder.py
|
||||||
homeassistant/components/*/alarmdecoder.py
|
homeassistant/components/*/alarmdecoder.py
|
||||||
|
|
||||||
|
homeassistant/components/ambient_station/__init__.py
|
||||||
|
homeassistant/components/ambient_station/sensor.py
|
||||||
|
|
||||||
homeassistant/components/amcrest.py
|
homeassistant/components/amcrest.py
|
||||||
homeassistant/components/*/amcrest.py
|
homeassistant/components/*/amcrest.py
|
||||||
|
|
||||||
@ -80,17 +83,24 @@ omit =
|
|||||||
homeassistant/components/digital_ocean.py
|
homeassistant/components/digital_ocean.py
|
||||||
homeassistant/components/*/digital_ocean.py
|
homeassistant/components/*/digital_ocean.py
|
||||||
|
|
||||||
|
homeassistant/components/danfoss_air/*
|
||||||
|
|
||||||
homeassistant/components/dominos.py
|
homeassistant/components/dominos.py
|
||||||
|
|
||||||
homeassistant/components/doorbird.py
|
homeassistant/components/doorbird.py
|
||||||
homeassistant/components/*/doorbird.py
|
homeassistant/components/*/doorbird.py
|
||||||
|
|
||||||
|
homeassistant/components/dovado/*
|
||||||
|
|
||||||
homeassistant/components/dweet.py
|
homeassistant/components/dweet.py
|
||||||
homeassistant/components/*/dweet.py
|
homeassistant/components/*/dweet.py
|
||||||
|
|
||||||
homeassistant/components/eight_sleep.py
|
homeassistant/components/eight_sleep.py
|
||||||
homeassistant/components/*/eight_sleep.py
|
homeassistant/components/*/eight_sleep.py
|
||||||
|
|
||||||
|
homeassistant/components/ecoal_boiler.py
|
||||||
|
homeassistant/components/*/ecoal_boiler.py
|
||||||
|
|
||||||
homeassistant/components/ecobee.py
|
homeassistant/components/ecobee.py
|
||||||
homeassistant/components/*/ecobee.py
|
homeassistant/components/*/ecobee.py
|
||||||
|
|
||||||
@ -163,13 +173,13 @@ omit =
|
|||||||
homeassistant/components/hlk_sw16.py
|
homeassistant/components/hlk_sw16.py
|
||||||
homeassistant/components/*/hlk_sw16.py
|
homeassistant/components/*/hlk_sw16.py
|
||||||
|
|
||||||
homeassistant/components/homekit_controller/__init__.py
|
homeassistant/components/homekit_controller/*
|
||||||
homeassistant/components/*/homekit_controller.py
|
|
||||||
|
|
||||||
homeassistant/components/homematic/__init__.py
|
homeassistant/components/homematic/__init__.py
|
||||||
homeassistant/components/*/homematic.py
|
homeassistant/components/*/homematic.py
|
||||||
|
|
||||||
homeassistant/components/homematicip_cloud.py
|
homeassistant/components/homematicip_cloud/hap.py
|
||||||
|
homeassistant/components/homematicip_cloud/device.py
|
||||||
homeassistant/components/*/homematicip_cloud.py
|
homeassistant/components/*/homematicip_cloud.py
|
||||||
|
|
||||||
homeassistant/components/homeworks.py
|
homeassistant/components/homeworks.py
|
||||||
@ -385,6 +395,9 @@ omit =
|
|||||||
homeassistant/components/tradfri.py
|
homeassistant/components/tradfri.py
|
||||||
homeassistant/components/*/tradfri.py
|
homeassistant/components/*/tradfri.py
|
||||||
|
|
||||||
|
homeassistant/components/transmission.py
|
||||||
|
homeassistant/components/*/transmission.py
|
||||||
|
|
||||||
homeassistant/components/notify/twilio_sms.py
|
homeassistant/components/notify/twilio_sms.py
|
||||||
homeassistant/components/notify/twilio_call.py
|
homeassistant/components/notify/twilio_call.py
|
||||||
|
|
||||||
@ -443,15 +456,19 @@ omit =
|
|||||||
homeassistant/components/zha/sensor.py
|
homeassistant/components/zha/sensor.py
|
||||||
homeassistant/components/zha/switch.py
|
homeassistant/components/zha/switch.py
|
||||||
homeassistant/components/zha/api.py
|
homeassistant/components/zha/api.py
|
||||||
homeassistant/components/zha/entities/*
|
homeassistant/components/zha/entity.py
|
||||||
homeassistant/components/zha/helpers.py
|
homeassistant/components/zha/device_entity.py
|
||||||
|
homeassistant/components/zha/core/helpers.py
|
||||||
|
homeassistant/components/zha/core/const.py
|
||||||
|
homeassistant/components/zha/core/device.py
|
||||||
|
homeassistant/components/zha/core/listeners.py
|
||||||
|
homeassistant/components/zha/core/gateway.py
|
||||||
homeassistant/components/*/zha.py
|
homeassistant/components/*/zha.py
|
||||||
|
|
||||||
homeassistant/components/zigbee.py
|
homeassistant/components/zigbee.py
|
||||||
homeassistant/components/*/zigbee.py
|
homeassistant/components/*/zigbee.py
|
||||||
|
|
||||||
homeassistant/components/zoneminder/*
|
homeassistant/components/zoneminder/*
|
||||||
homeassistant/components/*/zoneminder.py
|
|
||||||
|
|
||||||
homeassistant/components/tuya.py
|
homeassistant/components/tuya.py
|
||||||
homeassistant/components/*/tuya.py
|
homeassistant/components/*/tuya.py
|
||||||
@ -460,6 +477,7 @@ omit =
|
|||||||
homeassistant/components/*/spider.py
|
homeassistant/components/*/spider.py
|
||||||
|
|
||||||
homeassistant/components/air_quality/opensensemap.py
|
homeassistant/components/air_quality/opensensemap.py
|
||||||
|
homeassistant/components/air_quality/nilu.py
|
||||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||||
homeassistant/components/alarm_control_panel/canary.py
|
homeassistant/components/alarm_control_panel/canary.py
|
||||||
homeassistant/components/alarm_control_panel/concord232.py
|
homeassistant/components/alarm_control_panel/concord232.py
|
||||||
@ -552,6 +570,7 @@ omit =
|
|||||||
homeassistant/components/device_tracker/sky_hub.py
|
homeassistant/components/device_tracker/sky_hub.py
|
||||||
homeassistant/components/device_tracker/snmp.py
|
homeassistant/components/device_tracker/snmp.py
|
||||||
homeassistant/components/device_tracker/swisscom.py
|
homeassistant/components/device_tracker/swisscom.py
|
||||||
|
homeassistant/components/device_tracker/synology_srm.py
|
||||||
homeassistant/components/device_tracker/tado.py
|
homeassistant/components/device_tracker/tado.py
|
||||||
homeassistant/components/device_tracker/thomson.py
|
homeassistant/components/device_tracker/thomson.py
|
||||||
homeassistant/components/device_tracker/tile.py
|
homeassistant/components/device_tracker/tile.py
|
||||||
@ -574,6 +593,7 @@ omit =
|
|||||||
homeassistant/components/image_processing/dlib_face_identify.py
|
homeassistant/components/image_processing/dlib_face_identify.py
|
||||||
homeassistant/components/image_processing/seven_segments.py
|
homeassistant/components/image_processing/seven_segments.py
|
||||||
homeassistant/components/image_processing/tensorflow.py
|
homeassistant/components/image_processing/tensorflow.py
|
||||||
|
homeassistant/components/image_processing/qrcode.py
|
||||||
homeassistant/components/keyboard_remote.py
|
homeassistant/components/keyboard_remote.py
|
||||||
homeassistant/components/keyboard.py
|
homeassistant/components/keyboard.py
|
||||||
homeassistant/components/light/avion.py
|
homeassistant/components/light/avion.py
|
||||||
@ -581,6 +601,7 @@ omit =
|
|||||||
homeassistant/components/light/blinkt.py
|
homeassistant/components/light/blinkt.py
|
||||||
homeassistant/components/light/decora_wifi.py
|
homeassistant/components/light/decora_wifi.py
|
||||||
homeassistant/components/light/decora.py
|
homeassistant/components/light/decora.py
|
||||||
|
homeassistant/components/light/everlights.py
|
||||||
homeassistant/components/light/flux_led.py
|
homeassistant/components/light/flux_led.py
|
||||||
homeassistant/components/light/futurenow.py
|
homeassistant/components/light/futurenow.py
|
||||||
homeassistant/components/light/greenwave.py
|
homeassistant/components/light/greenwave.py
|
||||||
@ -719,7 +740,6 @@ omit =
|
|||||||
homeassistant/components/sensor/aftership.py
|
homeassistant/components/sensor/aftership.py
|
||||||
homeassistant/components/sensor/airvisual.py
|
homeassistant/components/sensor/airvisual.py
|
||||||
homeassistant/components/sensor/alpha_vantage.py
|
homeassistant/components/sensor/alpha_vantage.py
|
||||||
homeassistant/components/sensor/ambient_station.py
|
|
||||||
homeassistant/components/sensor/arest.py
|
homeassistant/components/sensor/arest.py
|
||||||
homeassistant/components/sensor/arwn.py
|
homeassistant/components/sensor/arwn.py
|
||||||
homeassistant/components/sensor/bbox.py
|
homeassistant/components/sensor/bbox.py
|
||||||
@ -744,7 +764,6 @@ omit =
|
|||||||
homeassistant/components/sensor/dht.py
|
homeassistant/components/sensor/dht.py
|
||||||
homeassistant/components/sensor/discogs.py
|
homeassistant/components/sensor/discogs.py
|
||||||
homeassistant/components/sensor/dnsip.py
|
homeassistant/components/sensor/dnsip.py
|
||||||
homeassistant/components/sensor/dovado.py
|
|
||||||
homeassistant/components/sensor/domain_expiry.py
|
homeassistant/components/sensor/domain_expiry.py
|
||||||
homeassistant/components/sensor/dte_energy_bridge.py
|
homeassistant/components/sensor/dte_energy_bridge.py
|
||||||
homeassistant/components/sensor/dublin_bus_transport.py
|
homeassistant/components/sensor/dublin_bus_transport.py
|
||||||
@ -781,6 +800,7 @@ omit =
|
|||||||
homeassistant/components/sensor/hp_ilo.py
|
homeassistant/components/sensor/hp_ilo.py
|
||||||
homeassistant/components/sensor/htu21d.py
|
homeassistant/components/sensor/htu21d.py
|
||||||
homeassistant/components/sensor/upnp.py
|
homeassistant/components/sensor/upnp.py
|
||||||
|
homeassistant/components/sensor/iliad_italy.py
|
||||||
homeassistant/components/sensor/imap_email_content.py
|
homeassistant/components/sensor/imap_email_content.py
|
||||||
homeassistant/components/sensor/imap.py
|
homeassistant/components/sensor/imap.py
|
||||||
homeassistant/components/sensor/influxdb.py
|
homeassistant/components/sensor/influxdb.py
|
||||||
@ -835,7 +855,9 @@ omit =
|
|||||||
homeassistant/components/sensor/qnap.py
|
homeassistant/components/sensor/qnap.py
|
||||||
homeassistant/components/sensor/radarr.py
|
homeassistant/components/sensor/radarr.py
|
||||||
homeassistant/components/sensor/rainbird.py
|
homeassistant/components/sensor/rainbird.py
|
||||||
|
homeassistant/components/sensor/recollect_waste.py
|
||||||
homeassistant/components/sensor/ripple.py
|
homeassistant/components/sensor/ripple.py
|
||||||
|
homeassistant/components/sensor/rova.py
|
||||||
homeassistant/components/sensor/rtorrent.py
|
homeassistant/components/sensor/rtorrent.py
|
||||||
homeassistant/components/sensor/ruter.py
|
homeassistant/components/sensor/ruter.py
|
||||||
homeassistant/components/sensor/scrape.py
|
homeassistant/components/sensor/scrape.py
|
||||||
@ -874,7 +896,6 @@ omit =
|
|||||||
homeassistant/components/sensor/time_date.py
|
homeassistant/components/sensor/time_date.py
|
||||||
homeassistant/components/sensor/torque.py
|
homeassistant/components/sensor/torque.py
|
||||||
homeassistant/components/sensor/trafikverket_weatherstation.py
|
homeassistant/components/sensor/trafikverket_weatherstation.py
|
||||||
homeassistant/components/sensor/transmission.py
|
|
||||||
homeassistant/components/sensor/travisci.py
|
homeassistant/components/sensor/travisci.py
|
||||||
homeassistant/components/sensor/twitch.py
|
homeassistant/components/sensor/twitch.py
|
||||||
homeassistant/components/sensor/uber.py
|
homeassistant/components/sensor/uber.py
|
||||||
@ -919,7 +940,6 @@ omit =
|
|||||||
homeassistant/components/switch/switchmate.py
|
homeassistant/components/switch/switchmate.py
|
||||||
homeassistant/components/switch/telnet.py
|
homeassistant/components/switch/telnet.py
|
||||||
homeassistant/components/switch/tplink.py
|
homeassistant/components/switch/tplink.py
|
||||||
homeassistant/components/switch/transmission.py
|
|
||||||
homeassistant/components/switch/vesync.py
|
homeassistant/components/switch/vesync.py
|
||||||
homeassistant/components/telegram_bot/*
|
homeassistant/components/telegram_bot/*
|
||||||
homeassistant/components/thingspeak.py
|
homeassistant/components/thingspeak.py
|
||||||
|
@ -95,6 +95,7 @@ homeassistant/components/notify/syslog.py @fabaff
|
|||||||
homeassistant/components/notify/xmpp.py @fabaff
|
homeassistant/components/notify/xmpp.py @fabaff
|
||||||
homeassistant/components/notify/yessssms.py @flowolf
|
homeassistant/components/notify/yessssms.py @flowolf
|
||||||
homeassistant/components/plant.py @ChristianKuehnel
|
homeassistant/components/plant.py @ChristianKuehnel
|
||||||
|
homeassistant/components/remote/harmony.py @ehendrix23
|
||||||
homeassistant/components/scene/lifx_cloud.py @amelchio
|
homeassistant/components/scene/lifx_cloud.py @amelchio
|
||||||
homeassistant/components/sensor/airvisual.py @bachya
|
homeassistant/components/sensor/airvisual.py @bachya
|
||||||
homeassistant/components/sensor/alpha_vantage.py @fabaff
|
homeassistant/components/sensor/alpha_vantage.py @fabaff
|
||||||
@ -152,6 +153,7 @@ homeassistant/components/weather/openweathermap.py @fabaff
|
|||||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||||
|
|
||||||
# A
|
# A
|
||||||
|
homeassistant/components/ambient_station/* @bachya
|
||||||
homeassistant/components/arduino.py @fabaff
|
homeassistant/components/arduino.py @fabaff
|
||||||
homeassistant/components/*/arduino.py @fabaff
|
homeassistant/components/*/arduino.py @fabaff
|
||||||
homeassistant/components/*/arest.py @fabaff
|
homeassistant/components/*/arest.py @fabaff
|
||||||
@ -234,6 +236,7 @@ homeassistant/components/*/rfxtrx.py @danielhiversen
|
|||||||
|
|
||||||
# S
|
# S
|
||||||
homeassistant/components/simplisafe/* @bachya
|
homeassistant/components/simplisafe/* @bachya
|
||||||
|
homeassistant/components/smartthings/* @andrewsayre
|
||||||
|
|
||||||
# T
|
# T
|
||||||
homeassistant/components/tahoma.py @philklei
|
homeassistant/components/tahoma.py @philklei
|
||||||
@ -268,8 +271,7 @@ homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
|||||||
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
||||||
|
|
||||||
# Z
|
# Z
|
||||||
homeassistant/components/zoneminder/ @rohankapoorcom
|
homeassistant/components/zoneminder/* @rohankapoorcom
|
||||||
homeassistant/components/*/zoneminder.py @rohankapoorcom
|
|
||||||
|
|
||||||
# Other code
|
# Other code
|
||||||
homeassistant/scripts/check_config.py @kellerza
|
homeassistant/scripts/check_config.py @kellerza
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import base64
|
import base64
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List, Optional, cast
|
|
||||||
|
from typing import Any, Dict, List, Optional, Set, cast # noqa: F401
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -52,6 +53,9 @@ class Data:
|
|||||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||||
private=True)
|
private=True)
|
||||||
self._data = None # type: Optional[Dict[str, Any]]
|
self._data = None # type: Optional[Dict[str, Any]]
|
||||||
|
# Legacy mode will allow usernames to start/end with whitespace
|
||||||
|
# and will compare usernames case-insensitive.
|
||||||
|
# Remove in 2020 or when we launch 1.0.
|
||||||
self.is_legacy = False
|
self.is_legacy = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -60,7 +64,7 @@ class Data:
|
|||||||
if self.is_legacy:
|
if self.is_legacy:
|
||||||
return username
|
return username
|
||||||
|
|
||||||
return username.strip()
|
return username.strip().casefold()
|
||||||
|
|
||||||
async def async_load(self) -> None:
|
async def async_load(self) -> None:
|
||||||
"""Load stored data."""
|
"""Load stored data."""
|
||||||
@ -71,9 +75,26 @@ class Data:
|
|||||||
'users': []
|
'users': []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seen = set() # type: Set[str]
|
||||||
|
|
||||||
for user in data['users']:
|
for user in data['users']:
|
||||||
username = user['username']
|
username = user['username']
|
||||||
|
|
||||||
|
# check if we have duplicates
|
||||||
|
folded = username.casefold()
|
||||||
|
|
||||||
|
if folded in seen:
|
||||||
|
self.is_legacy = True
|
||||||
|
|
||||||
|
logging.getLogger(__name__).warning(
|
||||||
|
"Home Assistant auth provider is running in legacy mode "
|
||||||
|
"because we detected usernames that are case-insensitive"
|
||||||
|
"equivalent. Please change the username: '%s'.", username)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
seen.add(folded)
|
||||||
|
|
||||||
# check if we have unstripped usernames
|
# check if we have unstripped usernames
|
||||||
if username != username.strip():
|
if username != username.strip():
|
||||||
self.is_legacy = True
|
self.is_legacy = True
|
||||||
@ -81,7 +102,7 @@ class Data:
|
|||||||
logging.getLogger(__name__).warning(
|
logging.getLogger(__name__).warning(
|
||||||
"Home Assistant auth provider is running in legacy mode "
|
"Home Assistant auth provider is running in legacy mode "
|
||||||
"because we detected usernames that start or end in a "
|
"because we detected usernames that start or end in a "
|
||||||
"space. Please change the username.")
|
"space. Please change the username: '%s'.", username)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -103,7 +124,7 @@ class Data:
|
|||||||
|
|
||||||
# Compare all users to avoid timing attacks.
|
# Compare all users to avoid timing attacks.
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
if username == user['username']:
|
if self.normalize_username(user['username']) == username:
|
||||||
found = user
|
found = user
|
||||||
|
|
||||||
if found is None:
|
if found is None:
|
||||||
|
@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
ATTR_AQI = 'air_quality_index'
|
ATTR_AQI = 'air_quality_index'
|
||||||
ATTR_ATTRIBUTION = 'attribution'
|
ATTR_ATTRIBUTION = 'attribution'
|
||||||
ATTR_C02 = 'carbon_dioxide'
|
ATTR_CO2 = 'carbon_dioxide'
|
||||||
ATTR_CO = 'carbon_monoxide'
|
ATTR_CO = 'carbon_monoxide'
|
||||||
ATTR_N2O = 'nitrogen_oxide'
|
ATTR_N2O = 'nitrogen_oxide'
|
||||||
ATTR_NO = 'nitrogen_monoxide'
|
ATTR_NO = 'nitrogen_monoxide'
|
||||||
@ -35,7 +35,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
|
|||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
'air_quality_index': ATTR_AQI,
|
'air_quality_index': ATTR_AQI,
|
||||||
'attribution': ATTR_ATTRIBUTION,
|
'attribution': ATTR_ATTRIBUTION,
|
||||||
'carbon_dioxide': ATTR_C02,
|
'carbon_dioxide': ATTR_CO2,
|
||||||
'carbon_monoxide': ATTR_CO,
|
'carbon_monoxide': ATTR_CO,
|
||||||
'nitrogen_oxide': ATTR_N2O,
|
'nitrogen_oxide': ATTR_N2O,
|
||||||
'nitrogen_monoxide': ATTR_NO,
|
'nitrogen_monoxide': ATTR_NO,
|
||||||
|
252
homeassistant/components/air_quality/nilu.py
Normal file
252
homeassistant/components/air_quality/nilu.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
"""
|
||||||
|
Sensor for checking the air quality around Norway.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/air_quality.nilu/
|
||||||
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.air_quality import (
|
||||||
|
PLATFORM_SCHEMA, AirQualityEntity)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_SHOW_ON_MAP)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
|
REQUIREMENTS = ['niluclient==0.1.2']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_AREA = 'area'
|
||||||
|
ATTR_POLLUTION_INDEX = 'nilu_pollution_index'
|
||||||
|
ATTRIBUTION = "Data provided by luftkvalitet.info and nilu.no"
|
||||||
|
|
||||||
|
CONF_AREA = 'area'
|
||||||
|
CONF_STATION = 'stations'
|
||||||
|
|
||||||
|
DEFAULT_NAME = 'NILU'
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(minutes=30)
|
||||||
|
|
||||||
|
CONF_ALLOWED_AREAS = [
|
||||||
|
'Bergen',
|
||||||
|
'Birkenes',
|
||||||
|
'Bodø',
|
||||||
|
'Brumunddal',
|
||||||
|
'Bærum',
|
||||||
|
'Drammen',
|
||||||
|
'Elverum',
|
||||||
|
'Fredrikstad',
|
||||||
|
'Gjøvik',
|
||||||
|
'Grenland',
|
||||||
|
'Halden',
|
||||||
|
'Hamar',
|
||||||
|
'Harstad',
|
||||||
|
'Hurdal',
|
||||||
|
'Karasjok',
|
||||||
|
'Kristiansand',
|
||||||
|
'Kårvatn',
|
||||||
|
'Lillehammer',
|
||||||
|
'Lillesand',
|
||||||
|
'Lillestrøm',
|
||||||
|
'Lørenskog',
|
||||||
|
'Mo i Rana',
|
||||||
|
'Moss',
|
||||||
|
'Narvik',
|
||||||
|
'Oslo',
|
||||||
|
'Prestebakke',
|
||||||
|
'Sandve',
|
||||||
|
'Sarpsborg',
|
||||||
|
'Stavanger',
|
||||||
|
'Sør-Varanger',
|
||||||
|
'Tromsø',
|
||||||
|
'Trondheim',
|
||||||
|
'Tustervatn',
|
||||||
|
'Zeppelinfjellet',
|
||||||
|
'Ålesund',
|
||||||
|
]
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Inclusive(CONF_LATITUDE, 'coordinates',
|
||||||
|
'Latitude and longitude must exist together'): cv.latitude,
|
||||||
|
vol.Inclusive(CONF_LONGITUDE, 'coordinates',
|
||||||
|
'Latitude and longitude must exist together'): cv.longitude,
|
||||||
|
vol.Exclusive(CONF_AREA, 'station_collection',
|
||||||
|
'Can only configure one specific station or '
|
||||||
|
'stations in a specific area pr sensor. '
|
||||||
|
'Please only configure station or area.'
|
||||||
|
): vol.All(cv.string, vol.In(CONF_ALLOWED_AREAS)),
|
||||||
|
vol.Exclusive(CONF_STATION, 'station_collection',
|
||||||
|
'Can only configure one specific station or '
|
||||||
|
'stations in a specific area pr sensor. '
|
||||||
|
'Please only configure station or area.'
|
||||||
|
): vol.All(cv.ensure_list, [cv.string]),
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the NILU air quality sensor."""
|
||||||
|
import niluclient as nilu
|
||||||
|
name = config.get(CONF_NAME)
|
||||||
|
area = config.get(CONF_AREA)
|
||||||
|
stations = config.get(CONF_STATION)
|
||||||
|
show_on_map = config.get(CONF_SHOW_ON_MAP)
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
|
||||||
|
if area:
|
||||||
|
stations = nilu.lookup_stations_in_area(area)
|
||||||
|
elif not area and not stations:
|
||||||
|
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||||
|
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||||
|
location_client = nilu.create_location_client(latitude, longitude)
|
||||||
|
stations = location_client.station_names
|
||||||
|
|
||||||
|
for station in stations:
|
||||||
|
client = NiluData(nilu.create_station_client(station))
|
||||||
|
client.update()
|
||||||
|
if client.data.sensors:
|
||||||
|
sensors.append(NiluSensor(client, name, show_on_map))
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("%s didn't give any sensors results", station)
|
||||||
|
|
||||||
|
add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class NiluData:
|
||||||
|
"""Class for handling the data retrieval."""
|
||||||
|
|
||||||
|
def __init__(self, api):
|
||||||
|
"""Initialize the data object."""
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
"""Get data cached in client."""
|
||||||
|
return self.api.data
|
||||||
|
|
||||||
|
@Throttle(SCAN_INTERVAL)
|
||||||
|
def update(self):
|
||||||
|
"""Get the latest data from nilu API."""
|
||||||
|
self.api.update()
|
||||||
|
|
||||||
|
|
||||||
|
class NiluSensor(AirQualityEntity):
|
||||||
|
"""Single nilu station air sensor."""
|
||||||
|
|
||||||
|
def __init__(self, api_data: NiluData, name: str, show_on_map: bool):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self._api = api_data
|
||||||
|
self._name = "{} {}".format(name, api_data.data.name)
|
||||||
|
self._max_aqi = None
|
||||||
|
self._attrs = {}
|
||||||
|
|
||||||
|
if show_on_map:
|
||||||
|
self._attrs[CONF_LATITUDE] = api_data.data.latitude
|
||||||
|
self._attrs[CONF_LONGITUDE] = api_data.data.longitude
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attribution(self) -> str:
|
||||||
|
"""Return the attribution."""
|
||||||
|
return ATTRIBUTION
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self) -> dict:
|
||||||
|
"""Return other details about the sensor state."""
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def air_quality_index(self) -> str:
|
||||||
|
"""Return the Air Quality Index (AQI)."""
|
||||||
|
return self._max_aqi
|
||||||
|
|
||||||
|
@property
|
||||||
|
def carbon_monoxide(self) -> str:
|
||||||
|
"""Return the CO (carbon monoxide) level."""
|
||||||
|
from niluclient import CO
|
||||||
|
return self.get_component_state(CO)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def carbon_dioxide(self) -> str:
|
||||||
|
"""Return the CO2 (carbon dioxide) level."""
|
||||||
|
from niluclient import CO2
|
||||||
|
return self.get_component_state(CO2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nitrogen_oxide(self) -> str:
|
||||||
|
"""Return the N2O (nitrogen oxide) level."""
|
||||||
|
from niluclient import NOX
|
||||||
|
return self.get_component_state(NOX)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nitrogen_monoxide(self) -> str:
|
||||||
|
"""Return the NO (nitrogen monoxide) level."""
|
||||||
|
from niluclient import NO
|
||||||
|
return self.get_component_state(NO)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nitrogen_dioxide(self) -> str:
|
||||||
|
"""Return the NO2 (nitrogen dioxide) level."""
|
||||||
|
from niluclient import NO2
|
||||||
|
return self.get_component_state(NO2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ozone(self) -> str:
|
||||||
|
"""Return the O3 (ozone) level."""
|
||||||
|
from niluclient import OZONE
|
||||||
|
return self.get_component_state(OZONE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_2_5(self) -> str:
|
||||||
|
"""Return the particulate matter 2.5 level."""
|
||||||
|
from niluclient import PM25
|
||||||
|
return self.get_component_state(PM25)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_10(self) -> str:
|
||||||
|
"""Return the particulate matter 10 level."""
|
||||||
|
from niluclient import PM10
|
||||||
|
return self.get_component_state(PM10)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def particulate_matter_0_1(self) -> str:
|
||||||
|
"""Return the particulate matter 0.1 level."""
|
||||||
|
from niluclient import PM1
|
||||||
|
return self.get_component_state(PM1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sulphur_dioxide(self) -> str:
|
||||||
|
"""Return the SO2 (sulphur dioxide) level."""
|
||||||
|
from niluclient import SO2
|
||||||
|
return self.get_component_state(SO2)
|
||||||
|
|
||||||
|
def get_component_state(self, component_name: str) -> str:
|
||||||
|
"""Return formatted value of specified component."""
|
||||||
|
if component_name in self._api.data.sensors:
|
||||||
|
sensor = self._api.data.sensors[component_name]
|
||||||
|
return sensor.value
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
"""Update the sensor."""
|
||||||
|
import niluclient as nilu
|
||||||
|
self._api.update()
|
||||||
|
|
||||||
|
sensors = self._api.data.sensors.values()
|
||||||
|
if sensors:
|
||||||
|
max_index = max([s.pollution_index for s in sensors])
|
||||||
|
self._max_aqi = max_index
|
||||||
|
self._attrs[ATTR_POLLUTION_INDEX] = \
|
||||||
|
nilu.POLLUTION_INDEX[self._max_aqi]
|
||||||
|
|
||||||
|
self._attrs[ATTR_AREA] = self._api.data.area
|
@ -13,7 +13,8 @@ from homeassistant.const import (
|
|||||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
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, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
|
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
|
||||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
from homeassistant.helpers.config_validation import ( # noqa
|
||||||
|
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
@ -6,9 +6,9 @@ https://home-assistant.io/components/alarm_control_panel.abode/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
||||||
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||||
STATE_ALARM_DISARMED)
|
STATE_ALARM_DISARMED)
|
||||||
@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities(alarm_devices)
|
add_entities(alarm_devices)
|
||||||
|
|
||||||
|
|
||||||
class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
||||||
"""An alarm_control_panel implementation for Abode."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
def __init__(self, data, device, name):
|
def __init__(self, data, device, name):
|
||||||
@ -57,6 +57,11 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
|||||||
state = None
|
state = None
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def code_format(self):
|
||||||
|
"""Return one or more digits/characters."""
|
||||||
|
return alarm.FORMAT_NUMBER
|
||||||
|
|
||||||
def alarm_disarm(self, code=None):
|
def alarm_disarm(self, code=None):
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
self._device.set_standby()
|
self._device.set_standby()
|
||||||
|
@ -13,7 +13,7 @@ import homeassistant.components.alarm_control_panel as alarm
|
|||||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self._websession = async_get_clientsession(self._hass)
|
self._websession = async_get_clientsession(self._hass)
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
self._alarm = Alarmdotcom(
|
self._alarm = Alarmdotcom(
|
||||||
username, password, self._websession, hass.loop)
|
username, password, self._websession, hass.loop)
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||||||
return STATE_ALARM_ARMED_HOME
|
return STATE_ALARM_ARMED_HOME
|
||||||
if self._alarm.state.lower() == 'armed away':
|
if self._alarm.state.lower() == 'armed away':
|
||||||
return STATE_ALARM_ARMED_AWAY
|
return STATE_ALARM_ARMED_AWAY
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
|
@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/alarm_control_panel.concord232/
|
https://home-assistant.io/components/alarm_control_panel.concord232/
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.components.alarm_control_panel as alarm
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
|
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
REQUIREMENTS = ['concord232==0.15']
|
REQUIREMENTS = ['concord232==0.15']
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ DEFAULT_HOST = 'localhost'
|
|||||||
DEFAULT_NAME = 'CONCORD232'
|
DEFAULT_NAME = 'CONCORD232'
|
||||||
DEFAULT_PORT = 5007
|
DEFAULT_PORT = 5007
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=10)
|
SCAN_INTERVAL = datetime.timedelta(seconds=10)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||||
@ -44,33 +43,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
url = 'http://{}:{}'.format(host, port)
|
url = 'http://{}:{}'.format(host, port)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
add_entities([Concord232Alarm(hass, url, name)])
|
add_entities([Concord232Alarm(url, name)], True)
|
||||||
except requests.exceptions.ConnectionError as ex:
|
except requests.exceptions.ConnectionError as ex:
|
||||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class Concord232Alarm(alarm.AlarmControlPanel):
|
class Concord232Alarm(alarm.AlarmControlPanel):
|
||||||
"""Representation of the Concord232-based alarm panel."""
|
"""Representation of the Concord232-based alarm panel."""
|
||||||
|
|
||||||
def __init__(self, hass, url, name):
|
def __init__(self, url, name):
|
||||||
"""Initialize the Concord232 alarm panel."""
|
"""Initialize the Concord232 alarm panel."""
|
||||||
from concord232 import client as concord232_client
|
from concord232 import client as concord232_client
|
||||||
|
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
self._hass = hass
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._url = url
|
self._url = url
|
||||||
|
self._alarm = concord232_client.Client(self._url)
|
||||||
try:
|
|
||||||
client = concord232_client.Client(self._url)
|
|
||||||
except requests.exceptions.ConnectionError as ex:
|
|
||||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
|
||||||
|
|
||||||
self._alarm = client
|
|
||||||
self._alarm.partitions = self._alarm.list_partitions()
|
self._alarm.partitions = self._alarm.list_partitions()
|
||||||
self._alarm.last_partition_update = datetime.datetime.now()
|
self._alarm.last_partition_update = datetime.datetime.now()
|
||||||
self.update()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -94,22 +84,17 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
|||||||
except requests.exceptions.ConnectionError as ex:
|
except requests.exceptions.ConnectionError as ex:
|
||||||
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
|
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
|
||||||
dict(host=self._url, reason=ex))
|
dict(host=self._url, reason=ex))
|
||||||
newstate = STATE_UNKNOWN
|
return
|
||||||
except IndexError:
|
except IndexError:
|
||||||
_LOGGER.error("Concord232 reports no partitions")
|
_LOGGER.error("Concord232 reports no partitions")
|
||||||
newstate = STATE_UNKNOWN
|
return
|
||||||
|
|
||||||
if part['arming_level'] == 'Off':
|
if part['arming_level'] == 'Off':
|
||||||
newstate = STATE_ALARM_DISARMED
|
self._state = STATE_ALARM_DISARMED
|
||||||
elif 'Home' in part['arming_level']:
|
elif 'Home' in part['arming_level']:
|
||||||
newstate = STATE_ALARM_ARMED_HOME
|
self._state = STATE_ALARM_ARMED_HOME
|
||||||
else:
|
else:
|
||||||
newstate = STATE_ALARM_ARMED_AWAY
|
self._state = STATE_ALARM_ARMED_AWAY
|
||||||
|
|
||||||
if not newstate == self._state:
|
|
||||||
_LOGGER.info("State change from %s to %s", self._state, newstate)
|
|
||||||
self._state = newstate
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
def alarm_disarm(self, code=None):
|
def alarm_disarm(self, code=None):
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
|
@ -76,7 +76,7 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
|
|||||||
|
|
||||||
async def async_alarm_arm_home(self, code=None):
|
async def async_alarm_arm_home(self, code=None):
|
||||||
"""Send arm home command."""
|
"""Send arm home command."""
|
||||||
await self._home.set_security_zones_activation(True, False)
|
await self._home.set_security_zones_activation(False, True)
|
||||||
|
|
||||||
async def async_alarm_arm_away(self, code=None):
|
async def async_alarm_arm_away(self, code=None):
|
||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||||
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME,
|
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, CONF_NAME,
|
||||||
STATE_ALARM_ARMED_CUSTOM_BYPASS)
|
STATE_ALARM_ARMED_CUSTOM_BYPASS)
|
||||||
|
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
|||||||
self._name = name
|
self._name = name
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
self._client = TotalConnectClient.TotalConnectClient(
|
self._client = TotalConnectClient.TotalConnectClient(
|
||||||
username, password)
|
username, password)
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
|||||||
elif status == self._client.DISARMING:
|
elif status == self._client.DISARMING:
|
||||||
state = STATE_ALARM_DISARMING
|
state = STATE_ALARM_DISARMING
|
||||||
else:
|
else:
|
||||||
state = STATE_UNKNOWN
|
state = None
|
||||||
|
|
||||||
self._state = state
|
self._state = state
|
||||||
|
|
||||||
|
@ -11,8 +11,7 @@ import homeassistant.components.alarm_control_panel as alarm
|
|||||||
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
|
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
|
||||||
from homeassistant.components.verisure import HUB as hub
|
from homeassistant.components.verisure import HUB as hub
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||||
STATE_UNKNOWN)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the Verisure alarm panel."""
|
"""Initialize the Verisure alarm panel."""
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
||||||
self._changed_by = None
|
self._changed_by = None
|
||||||
|
|
||||||
|
@ -9,8 +9,7 @@ import logging
|
|||||||
import homeassistant.components.alarm_control_panel as alarm
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||||
STATE_UNKNOWN)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
|||||||
elif wink_state == "night":
|
elif wink_state == "night":
|
||||||
state = STATE_ALARM_ARMED_HOME
|
state = STATE_ALARM_ARMED_HOME
|
||||||
else:
|
else:
|
||||||
state = STATE_UNKNOWN
|
state = None
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def alarm_disarm(self, code=None):
|
def alarm_disarm(self, code=None):
|
||||||
|
@ -5,19 +5,19 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/alert/
|
https://home-assistant.io/components/alert/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
|
ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, DOMAIN as DOMAIN_NOTIFY)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
|
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
|
||||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
|
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
|
||||||
from homeassistant.helpers import service, event
|
from homeassistant.helpers import service, event
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -30,6 +30,8 @@ CONF_REPEAT = 'repeat'
|
|||||||
CONF_SKIP_FIRST = 'skip_first'
|
CONF_SKIP_FIRST = 'skip_first'
|
||||||
CONF_ALERT_MESSAGE = 'message'
|
CONF_ALERT_MESSAGE = 'message'
|
||||||
CONF_DONE_MESSAGE = 'done_message'
|
CONF_DONE_MESSAGE = 'done_message'
|
||||||
|
CONF_TITLE = 'title'
|
||||||
|
CONF_DATA = 'data'
|
||||||
|
|
||||||
DEFAULT_CAN_ACK = True
|
DEFAULT_CAN_ACK = True
|
||||||
DEFAULT_SKIP_FIRST = False
|
DEFAULT_SKIP_FIRST = False
|
||||||
@ -43,13 +45,14 @@ ALERT_SCHEMA = vol.Schema({
|
|||||||
vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean,
|
vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean,
|
||||||
vol.Optional(CONF_ALERT_MESSAGE): cv.template,
|
vol.Optional(CONF_ALERT_MESSAGE): cv.template,
|
||||||
vol.Optional(CONF_DONE_MESSAGE): cv.template,
|
vol.Optional(CONF_DONE_MESSAGE): cv.template,
|
||||||
|
vol.Optional(CONF_TITLE): cv.template,
|
||||||
|
vol.Optional(CONF_DATA): dict,
|
||||||
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
|
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA),
|
DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
ALERT_SERVICE_SCHEMA = vol.Schema({
|
ALERT_SERVICE_SCHEMA = vol.Schema({
|
||||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
})
|
})
|
||||||
@ -77,12 +80,14 @@ async def async_setup(hass, config):
|
|||||||
done_message_template = cfg.get(CONF_DONE_MESSAGE)
|
done_message_template = cfg.get(CONF_DONE_MESSAGE)
|
||||||
notifiers = cfg.get(CONF_NOTIFIERS)
|
notifiers = cfg.get(CONF_NOTIFIERS)
|
||||||
can_ack = cfg.get(CONF_CAN_ACK)
|
can_ack = cfg.get(CONF_CAN_ACK)
|
||||||
|
title_template = cfg.get(CONF_TITLE)
|
||||||
|
data = cfg.get(CONF_DATA)
|
||||||
|
|
||||||
entities.append(Alert(hass, object_id, name,
|
entities.append(Alert(hass, object_id, name,
|
||||||
watched_entity_id, alert_state, repeat,
|
watched_entity_id, alert_state, repeat,
|
||||||
skip_first, message_template,
|
skip_first, message_template,
|
||||||
done_message_template, notifiers,
|
done_message_template, notifiers,
|
||||||
can_ack))
|
can_ack, title_template, data))
|
||||||
|
|
||||||
if not entities:
|
if not entities:
|
||||||
return False
|
return False
|
||||||
@ -127,12 +132,14 @@ class Alert(ToggleEntity):
|
|||||||
|
|
||||||
def __init__(self, hass, entity_id, name, watched_entity_id,
|
def __init__(self, hass, entity_id, name, watched_entity_id,
|
||||||
state, repeat, skip_first, message_template,
|
state, repeat, skip_first, message_template,
|
||||||
done_message_template, notifiers, can_ack):
|
done_message_template, notifiers, can_ack, title_template,
|
||||||
|
data):
|
||||||
"""Initialize the alert."""
|
"""Initialize the alert."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
self._alert_state = state
|
self._alert_state = state
|
||||||
self._skip_first = skip_first
|
self._skip_first = skip_first
|
||||||
|
self._data = data
|
||||||
|
|
||||||
self._message_template = message_template
|
self._message_template = message_template
|
||||||
if self._message_template is not None:
|
if self._message_template is not None:
|
||||||
@ -142,6 +149,10 @@ class Alert(ToggleEntity):
|
|||||||
if self._done_message_template is not None:
|
if self._done_message_template is not None:
|
||||||
self._done_message_template.hass = hass
|
self._done_message_template.hass = hass
|
||||||
|
|
||||||
|
self._title_template = title_template
|
||||||
|
if self._title_template is not None:
|
||||||
|
self._title_template.hass = hass
|
||||||
|
|
||||||
self._notifiers = notifiers
|
self._notifiers = notifiers
|
||||||
self._can_ack = can_ack
|
self._can_ack = can_ack
|
||||||
|
|
||||||
@ -251,9 +262,20 @@ class Alert(ToggleEntity):
|
|||||||
await self._send_notification_message(message)
|
await self._send_notification_message(message)
|
||||||
|
|
||||||
async def _send_notification_message(self, message):
|
async def _send_notification_message(self, message):
|
||||||
|
|
||||||
|
msg_payload = {ATTR_MESSAGE: message}
|
||||||
|
|
||||||
|
if self._title_template is not None:
|
||||||
|
title = self._title_template.async_render()
|
||||||
|
msg_payload.update({ATTR_TITLE: title})
|
||||||
|
if self._data:
|
||||||
|
msg_payload.update({ATTR_DATA: self._data})
|
||||||
|
|
||||||
|
_LOGGER.debug(msg_payload)
|
||||||
|
|
||||||
for target in self._notifiers:
|
for target in self._notifiers:
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: message})
|
DOMAIN_NOTIFY, target, msg_payload)
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Async Unacknowledge alert."""
|
"""Async Unacknowledge alert."""
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada",
|
||||||
|
"invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es",
|
||||||
|
"no_devices": "No s'ha trobat cap dispositiu al compte"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Clau API",
|
||||||
|
"app_key": "Clau d'aplicaci\u00f3"
|
||||||
|
},
|
||||||
|
"title": "Introdueix la teva informaci\u00f3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Application Key and/or API Key already registered",
|
||||||
|
"invalid_key": "Invalid API Key and/or Application Key",
|
||||||
|
"no_devices": "No devices found in account"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Key",
|
||||||
|
"app_key": "Application Key"
|
||||||
|
},
|
||||||
|
"title": "Fill in your information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||||
|
"invalid_key": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||||
|
"no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API \ud0a4",
|
||||||
|
"app_key": "Application \ud0a4"
|
||||||
|
},
|
||||||
|
"title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert",
|
||||||
|
"invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel",
|
||||||
|
"no_devices": "Keng Apparater am Kont fonnt"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Schl\u00ebssel",
|
||||||
|
"app_key": "Applikatioun's Schl\u00ebssel"
|
||||||
|
},
|
||||||
|
"title": "F\u00ebllt \u00e4r Informatiounen aus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d",
|
||||||
|
"invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
|
||||||
|
"no_devices": "\u0412 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "\u041a\u043b\u044e\u0447 API",
|
||||||
|
"app_key": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ambient PWS"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a",
|
||||||
|
"invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548",
|
||||||
|
"no_devices": "\u5e33\u865f\u4e2d\u627e\u4e0d\u5230\u4efb\u4f55\u88dd\u7f6e"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API \u5bc6\u9470",
|
||||||
|
"app_key": "\u61c9\u7528\u5bc6\u9470"
|
||||||
|
},
|
||||||
|
"title": "\u586b\u5beb\u8cc7\u8a0a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "\u74b0\u5883 PWS"
|
||||||
|
}
|
||||||
|
}
|
205
homeassistant/components/ambient_station/__init__.py
Normal file
205
homeassistant/components/ambient_station/__init__.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
"""
|
||||||
|
Support for Ambient Weather Station Service.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/ambient_station/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
|
||||||
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
|
from .config_flow import configured_instances
|
||||||
|
from .const import (
|
||||||
|
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['aioambient==0.1.0']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_SOCKET_MIN_RETRY = 15
|
||||||
|
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
'24hourrainin': ('24 Hr Rain', 'in'),
|
||||||
|
'baromabsin': ('Abs Pressure', 'inHg'),
|
||||||
|
'baromrelin': ('Rel Pressure', 'inHg'),
|
||||||
|
'battout': ('Battery', ''),
|
||||||
|
'co2': ('co2', 'ppm'),
|
||||||
|
'dailyrainin': ('Daily Rain', 'in'),
|
||||||
|
'dewPoint': ('Dew Point', '°F'),
|
||||||
|
'eventrainin': ('Event Rain', 'in'),
|
||||||
|
'feelsLike': ('Feels Like', '°F'),
|
||||||
|
'hourlyrainin': ('Hourly Rain Rate', 'in/hr'),
|
||||||
|
'humidity': ('Humidity', '%'),
|
||||||
|
'humidityin': ('Humidity In', '%'),
|
||||||
|
'lastRain': ('Last Rain', ''),
|
||||||
|
'maxdailygust': ('Max Gust', 'mph'),
|
||||||
|
'monthlyrainin': ('Monthly Rain', 'in'),
|
||||||
|
'solarradiation': ('Solar Rad', 'W/m^2'),
|
||||||
|
'tempf': ('Temp', '°F'),
|
||||||
|
'tempinf': ('Inside Temp', '°F'),
|
||||||
|
'totalrainin': ('Lifetime Rain', 'in'),
|
||||||
|
'uv': ('uv', 'Index'),
|
||||||
|
'weeklyrainin': ('Weekly Rain', 'in'),
|
||||||
|
'winddir': ('Wind Dir', '°'),
|
||||||
|
'winddir_avg10m': ('Wind Dir Avg 10m', '°'),
|
||||||
|
'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'),
|
||||||
|
'windgustdir': ('Gust Dir', '°'),
|
||||||
|
'windgustmph': ('Wind Gust', 'mph'),
|
||||||
|
'windspdmph_avg10m': ('Wind Avg 10m', 'mph'),
|
||||||
|
'windspdmph_avg2m': ('Wind Avg 2m', 'mph'),
|
||||||
|
'windspeedmph': ('Wind Speed', 'mph'),
|
||||||
|
'yearlyrainin': ('Yearly Rain', 'in'),
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN:
|
||||||
|
vol.Schema({
|
||||||
|
vol.Required(CONF_APP_KEY):
|
||||||
|
cv.string,
|
||||||
|
vol.Required(CONF_API_KEY):
|
||||||
|
cv.string,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||||
|
})
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Ambient PWS component."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
if conf[CONF_APP_KEY] in configured_instances(hass):
|
||||||
|
return True
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={'source': SOURCE_IMPORT}, data=conf))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up the Ambient PWS as config entry."""
|
||||||
|
from aioambient import Client
|
||||||
|
from aioambient.errors import WebsocketConnectionError
|
||||||
|
|
||||||
|
session = aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ambient = AmbientStation(
|
||||||
|
hass,
|
||||||
|
config_entry,
|
||||||
|
Client(
|
||||||
|
config_entry.data[CONF_API_KEY],
|
||||||
|
config_entry.data[CONF_APP_KEY], session),
|
||||||
|
config_entry.data.get(
|
||||||
|
CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)))
|
||||||
|
hass.loop.create_task(ambient.ws_connect())
|
||||||
|
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient
|
||||||
|
except WebsocketConnectionError as err:
|
||||||
|
_LOGGER.error('Config entry failed: %s', err)
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect())
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload an Ambient PWS config entry."""
|
||||||
|
ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||||
|
hass.async_create_task(ambient.ws_disconnect())
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_unload(
|
||||||
|
config_entry, 'sensor')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientStation:
|
||||||
|
"""Define a class to handle the Ambient websocket."""
|
||||||
|
|
||||||
|
def __init__(self, hass, config_entry, client, monitored_conditions):
|
||||||
|
"""Initialize."""
|
||||||
|
self._config_entry = config_entry
|
||||||
|
self._hass = hass
|
||||||
|
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
|
||||||
|
self.client = client
|
||||||
|
self.monitored_conditions = monitored_conditions
|
||||||
|
self.stations = {}
|
||||||
|
|
||||||
|
async def ws_connect(self):
|
||||||
|
"""Register handlers and connect to the websocket."""
|
||||||
|
from aioambient.errors import WebsocketError
|
||||||
|
|
||||||
|
def on_connect():
|
||||||
|
"""Define a handler to fire when the websocket is connected."""
|
||||||
|
_LOGGER.info('Connected to websocket')
|
||||||
|
|
||||||
|
def on_data(data):
|
||||||
|
"""Define a handler to fire when the data is received."""
|
||||||
|
mac_address = data['macAddress']
|
||||||
|
if data != self.stations[mac_address][ATTR_LAST_DATA]:
|
||||||
|
_LOGGER.debug('New data received: %s', data)
|
||||||
|
self.stations[mac_address][ATTR_LAST_DATA] = data
|
||||||
|
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||||
|
|
||||||
|
def on_disconnect():
|
||||||
|
"""Define a handler to fire when the websocket is disconnected."""
|
||||||
|
_LOGGER.info('Disconnected from websocket')
|
||||||
|
|
||||||
|
def on_subscribed(data):
|
||||||
|
"""Define a handler to fire when the subscription is set."""
|
||||||
|
for station in data['devices']:
|
||||||
|
if station['macAddress'] in self.stations:
|
||||||
|
continue
|
||||||
|
|
||||||
|
_LOGGER.debug('New station subscription: %s', data)
|
||||||
|
|
||||||
|
self.stations[station['macAddress']] = {
|
||||||
|
ATTR_LAST_DATA: station['lastData'],
|
||||||
|
ATTR_LOCATION: station['info']['location'],
|
||||||
|
ATTR_NAME: station['info']['name'],
|
||||||
|
}
|
||||||
|
|
||||||
|
self._hass.async_create_task(
|
||||||
|
self._hass.config_entries.async_forward_entry_setup(
|
||||||
|
self._config_entry, 'sensor'))
|
||||||
|
|
||||||
|
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
|
||||||
|
|
||||||
|
self.client.websocket.on_connect(on_connect)
|
||||||
|
self.client.websocket.on_data(on_data)
|
||||||
|
self.client.websocket.on_disconnect(on_disconnect)
|
||||||
|
self.client.websocket.on_subscribed(on_subscribed)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.client.websocket.connect()
|
||||||
|
except WebsocketError as err:
|
||||||
|
_LOGGER.error("Error with the websocket connection: %s", err)
|
||||||
|
|
||||||
|
self._ws_reconnect_delay = min(
|
||||||
|
2 * self._ws_reconnect_delay, 480)
|
||||||
|
|
||||||
|
async_call_later(
|
||||||
|
self._hass, self._ws_reconnect_delay, self.ws_connect)
|
||||||
|
|
||||||
|
async def ws_disconnect(self):
|
||||||
|
"""Disconnect from the websocket."""
|
||||||
|
await self.client.websocket.disconnect()
|
72
homeassistant/components/ambient_station/config_flow.py
Normal file
72
homeassistant/components/ambient_station/config_flow.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
"""Config flow to configure the Ambient PWS component."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
|
from .const import CONF_APP_KEY, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def configured_instances(hass):
|
||||||
|
"""Return a set of configured Ambient PWS instances."""
|
||||||
|
return set(
|
||||||
|
entry.data[CONF_APP_KEY]
|
||||||
|
for entry in hass.config_entries.async_entries(DOMAIN))
|
||||||
|
|
||||||
|
|
||||||
|
@config_entries.HANDLERS.register(DOMAIN)
|
||||||
|
class AmbientStationFlowHandler(config_entries.ConfigFlow):
|
||||||
|
"""Handle an Ambient PWS config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
|
||||||
|
|
||||||
|
async def _show_form(self, errors=None):
|
||||||
|
"""Show the form to the user."""
|
||||||
|
data_schema = vol.Schema({
|
||||||
|
vol.Required(CONF_API_KEY): str,
|
||||||
|
vol.Required(CONF_APP_KEY): str,
|
||||||
|
})
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='user',
|
||||||
|
data_schema=data_schema,
|
||||||
|
errors=errors if errors else {},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a config entry from configuration.yaml."""
|
||||||
|
return await self.async_step_user(import_config)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the start of the config flow."""
|
||||||
|
from aioambient import Client
|
||||||
|
from aioambient.errors import AmbientError
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
return await self._show_form()
|
||||||
|
|
||||||
|
if user_input[CONF_APP_KEY] in configured_instances(self.hass):
|
||||||
|
return await self._show_form({CONF_APP_KEY: 'identifier_exists'})
|
||||||
|
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
client = Client(
|
||||||
|
user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session)
|
||||||
|
|
||||||
|
try:
|
||||||
|
devices = await client.api.get_devices()
|
||||||
|
except AmbientError:
|
||||||
|
return await self._show_form({'base': 'invalid_key'})
|
||||||
|
|
||||||
|
if not devices:
|
||||||
|
return await self._show_form({'base': 'no_devices'})
|
||||||
|
|
||||||
|
# The Application Key (which identifies each config entry) is too long
|
||||||
|
# to show nicely in the UI, so we take the first 12 characters (similar
|
||||||
|
# to how GitHub does it):
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input[CONF_APP_KEY][:12], data=user_input)
|
10
homeassistant/components/ambient_station/const.py
Normal file
10
homeassistant/components/ambient_station/const.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""Define constants for the Ambient PWS component."""
|
||||||
|
DOMAIN = 'ambient_station'
|
||||||
|
|
||||||
|
ATTR_LAST_DATA = 'last_data'
|
||||||
|
|
||||||
|
CONF_APP_KEY = 'app_key'
|
||||||
|
|
||||||
|
DATA_CLIENT = 'data_client'
|
||||||
|
|
||||||
|
TOPIC_UPDATE = 'update'
|
102
homeassistant/components/ambient_station/sensor.py
Normal file
102
homeassistant/components/ambient_station/sensor.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
"""
|
||||||
|
Support for Ambient Weather Station Service.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.ambient_station/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.ambient_station import SENSOR_TYPES
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.const import ATTR_NAME
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
|
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE
|
||||||
|
|
||||||
|
DEPENDENCIES = ['ambient_station']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(
|
||||||
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Set up an Ambient PWS sensor based on existing config."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
|
"""Set up an Ambient PWS sensor based on a config entry."""
|
||||||
|
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||||
|
|
||||||
|
sensor_list = []
|
||||||
|
for mac_address, station in ambient.stations.items():
|
||||||
|
for condition in ambient.monitored_conditions:
|
||||||
|
name, unit = SENSOR_TYPES[condition]
|
||||||
|
sensor_list.append(
|
||||||
|
AmbientWeatherSensor(
|
||||||
|
ambient, mac_address, station[ATTR_NAME], condition, name,
|
||||||
|
unit))
|
||||||
|
|
||||||
|
async_add_entities(sensor_list, True)
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientWeatherSensor(Entity):
|
||||||
|
"""Define an Ambient sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, ambient, mac_address, station_name, sensor_type, sensor_name,
|
||||||
|
unit):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self._ambient = ambient
|
||||||
|
self._async_unsub_dispatcher_connect = None
|
||||||
|
self._mac_address = mac_address
|
||||||
|
self._sensor_name = sensor_name
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._station_name = station_name
|
||||||
|
self._unit = unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return '{0}_{1}'.format(self._station_name, self._sensor_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Disable polling."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique, unchanging string that represents this sensor."""
|
||||||
|
return '{0}_{1}'.format(self._mac_address, self._sensor_name)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
@callback
|
||||||
|
def update():
|
||||||
|
"""Update the state."""
|
||||||
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
|
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||||
|
self.hass, TOPIC_UPDATE, update)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Disconnect dispatcher listener when removed."""
|
||||||
|
if self._async_unsub_dispatcher_connect:
|
||||||
|
self._async_unsub_dispatcher_connect()
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Fetch new state data for the sensor."""
|
||||||
|
self._state = self._ambient.stations[
|
||||||
|
self._mac_address][ATTR_LAST_DATA].get(self._sensor_type)
|
19
homeassistant/components/ambient_station/strings.json
Normal file
19
homeassistant/components/ambient_station/strings.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Ambient PWS",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Fill in your information",
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Key",
|
||||||
|
"app_key": "Application Key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Application Key and/or API Key already registered",
|
||||||
|
"invalid_key": "Invalid API Key and/or Application Key",
|
||||||
|
"no_devices": "No devices found in account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -123,8 +123,9 @@ ICON_MAP = {
|
|||||||
'whitebalance_lock': 'mdi:white-balance-auto'
|
'whitebalance_lock': 'mdi:white-balance-auto'
|
||||||
}
|
}
|
||||||
|
|
||||||
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
|
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active',
|
||||||
'overlay', 'torch', 'whitebalance_lock', 'video_recording']
|
'motion_detect', 'night_vision', 'overlay',
|
||||||
|
'torch', 'whitebalance_lock', 'video_recording']
|
||||||
|
|
||||||
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
|
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
|
||||||
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
|
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.discovery import async_load_platform
|
from homeassistant.helpers.discovery import async_load_platform
|
||||||
|
|
||||||
REQUIREMENTS = ['aioasuswrt==1.1.18']
|
REQUIREMENTS = ['aioasuswrt==1.1.20']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:",
|
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 us\u0142ug\u0119 powiadamiania:",
|
||||||
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"totp": {
|
"totp": {
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."
|
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistanta to\u010dna."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
"notify": {
|
"notify": {
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437."
|
"invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"setup": {
|
||||||
|
"title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,19 +15,23 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
|
|
||||||
DEPENDENCIES = ['mqtt']
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
CONF_ENCODING = 'encoding'
|
||||||
CONF_TOPIC = 'topic'
|
CONF_TOPIC = 'topic'
|
||||||
|
DEFAULT_ENCODING = 'utf-8'
|
||||||
|
|
||||||
TRIGGER_SCHEMA = vol.Schema({
|
TRIGGER_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
|
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
|
||||||
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
|
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
|
||||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
vol.Optional(CONF_PAYLOAD): cv.string,
|
||||||
|
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_trigger(hass, config, action, automation_info):
|
async def async_trigger(hass, config, action, automation_info):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
topic = config.get(CONF_TOPIC)
|
topic = config[CONF_TOPIC]
|
||||||
payload = config.get(CONF_PAYLOAD)
|
payload = config.get(CONF_PAYLOAD)
|
||||||
|
encoding = config[CONF_ENCODING] or None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
||||||
@ -50,5 +54,5 @@ async def async_trigger(hass, config, action, automation_info):
|
|||||||
})
|
})
|
||||||
|
|
||||||
remove = await mqtt.async_subscribe(
|
remove = await mqtt.async_subscribe(
|
||||||
hass, topic, mqtt_automation_listener)
|
hass, topic, mqtt_automation_listener, encoding=encoding)
|
||||||
return remove
|
return remove
|
||||||
|
@ -15,8 +15,6 @@ DEPENDENCIES = ['homematicip_cloud']
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STATE_SMOKE_OFF = 'IDLE_OFF'
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
@ -65,7 +63,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
|
|||||||
return True
|
return True
|
||||||
if self._device.windowState is None:
|
if self._device.windowState is None:
|
||||||
return None
|
return None
|
||||||
return self._device.windowState == WindowState.OPEN
|
return self._device.windowState != WindowState.CLOSED
|
||||||
|
|
||||||
|
|
||||||
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
|
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
|
||||||
@ -95,7 +93,9 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
|
|||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if smoke is detected."""
|
"""Return true if smoke is detected."""
|
||||||
return self._device.smokeDetectorAlarmType != STATE_SMOKE_OFF
|
from homematicip.base.enums import SmokeDetectorAlarmType
|
||||||
|
return (self._device.smokeDetectorAlarmType
|
||||||
|
!= SmokeDetectorAlarmType.IDLE_OFF)
|
||||||
|
|
||||||
|
|
||||||
class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice):
|
class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice):
|
||||||
|
@ -8,7 +8,6 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.components.maxcube import DATA_KEY
|
from homeassistant.components.maxcube import DATA_KEY
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ class MaxCubeShutter(BinarySensorDevice):
|
|||||||
self._sensor_type = 'window'
|
self._sensor_type = 'window'
|
||||||
self._rf_address = rf_address
|
self._rf_address = rf_address
|
||||||
self._cubehandle = handler
|
self._cubehandle = handler
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
|
@ -22,7 +22,7 @@ from homeassistant.helpers.entity import generate_entity_id
|
|||||||
from homeassistant.helpers.event import async_track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
from homeassistant.util import utcnow
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
REQUIREMENTS = ['numpy==1.15.4']
|
REQUIREMENTS = ['numpy==1.16.0']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
|
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
|
||||||
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
|
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
REQUIREMENTS = ['blinkpy==0.11.2']
|
REQUIREMENTS = ['blinkpy==0.12.1']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class AmcrestCam(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._ffmpeg.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ class ArloCam(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._ffmpeg.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class CanaryCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._ffmpeg.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class FFmpegCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._manager.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -213,7 +213,8 @@ class ONVIFHassCamera(Camera):
|
|||||||
if not self._input:
|
if not self._input:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary,
|
ffmpeg_manager = self.hass.data[DATA_FFMPEG]
|
||||||
|
stream = CameraMjpeg(ffmpeg_manager.binary,
|
||||||
loop=self.hass.loop)
|
loop=self.hass.loop)
|
||||||
await stream.open_camera(
|
await stream.open_camera(
|
||||||
self._input, extra_cmd=self._ffmpeg_arguments)
|
self._input, extra_cmd=self._ffmpeg_arguments)
|
||||||
@ -221,7 +222,7 @@ class ONVIFHassCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
ffmpeg_manager.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ class RingCam(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._ffmpeg.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
|
@ -161,6 +161,6 @@ class XiaomiCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._manager.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
@ -147,6 +147,6 @@ class YiCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
return await async_aiohttp_proxy_stream(
|
return await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
self._manager.ffmpeg_stream_content_type)
|
||||||
finally:
|
finally:
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||||
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
|
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
|
||||||
PRECISION_TENTHS)
|
PRECISION_TENTHS)
|
||||||
|
|
||||||
DEFAULT_MIN_TEMP = 7
|
DEFAULT_MIN_TEMP = 7
|
||||||
@ -208,7 +208,7 @@ class ClimateDevice(Entity):
|
|||||||
return self.current_operation
|
return self.current_operation
|
||||||
if self.is_on:
|
if self.is_on:
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self):
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
REQUIREMENTS = ['millheater==0.3.3']
|
REQUIREMENTS = ['millheater==0.3.4']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.components.climate import (
|
|||||||
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE)
|
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||||
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
|
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF)
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
DEPENDENCIES = ['nest']
|
DEPENDENCIES = ['nest']
|
||||||
@ -163,7 +163,7 @@ class NestThermostat(ClimateDevice):
|
|||||||
return self._mode
|
return self._mode
|
||||||
if self._mode == NEST_MODE_HEAT_COOL:
|
if self._mode == NEST_MODE_HEAT_COOL:
|
||||||
return STATE_AUTO
|
return STATE_AUTO
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
|
@ -219,6 +219,11 @@ class RadioThermostat(ClimateDevice):
|
|||||||
"""Return true if away mode is on."""
|
"""Return true if away mode is on."""
|
||||||
return self._away
|
return self._away
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if on."""
|
||||||
|
return self._tstate != STATE_IDLE
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update and validate the data from the thermostat."""
|
"""Update and validate the data from the thermostat."""
|
||||||
# Radio thermostats are very slow, and sometimes don't respond
|
# Radio thermostats are very slow, and sometimes don't respond
|
||||||
|
@ -106,6 +106,7 @@ async def async_setup(hass, config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
|
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
|
||||||
|
await auth_api.async_setup(hass, cloud)
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
|
||||||
await http_api.async_setup(hass)
|
await http_api.async_setup(hass)
|
||||||
return True
|
return True
|
||||||
@ -263,7 +264,7 @@ class Cloud:
|
|||||||
self.access_token = info['access_token']
|
self.access_token = info['access_token']
|
||||||
self.refresh_token = info['refresh_token']
|
self.refresh_token = info['refresh_token']
|
||||||
|
|
||||||
self.hass.add_job(self.iot.connect())
|
self.hass.async_create_task(self.iot.connect())
|
||||||
|
|
||||||
def _decode_claims(self, token): # pylint: disable=no-self-use
|
def _decode_claims(self, token): # pylint: disable=no-self-use
|
||||||
"""Decode the claims in a token."""
|
"""Decode the claims in a token."""
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
"""Package to communicate with the authentication API."""
|
"""Package to communicate with the authentication API."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CloudError(Exception):
|
class CloudError(Exception):
|
||||||
@ -39,6 +45,40 @@ AWS_EXCEPTIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, cloud):
|
||||||
|
"""Configure the auth api."""
|
||||||
|
refresh_task = None
|
||||||
|
|
||||||
|
async def handle_token_refresh():
|
||||||
|
"""Handle Cloud access token refresh."""
|
||||||
|
sleep_time = 5
|
||||||
|
sleep_time = random.randint(2400, 3600)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(sleep_time)
|
||||||
|
await hass.async_add_executor_job(renew_access_token, cloud)
|
||||||
|
except CloudError as err:
|
||||||
|
_LOGGER.error("Can't refresh cloud token: %s", err)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# Task is canceled, stop it.
|
||||||
|
break
|
||||||
|
|
||||||
|
sleep_time = random.randint(3100, 3600)
|
||||||
|
|
||||||
|
async def on_connect():
|
||||||
|
"""When the instance is connected."""
|
||||||
|
nonlocal refresh_task
|
||||||
|
refresh_task = hass.async_create_task(handle_token_refresh())
|
||||||
|
|
||||||
|
async def on_disconnect():
|
||||||
|
"""When the instance is disconnected."""
|
||||||
|
nonlocal refresh_task
|
||||||
|
refresh_task.cancel()
|
||||||
|
|
||||||
|
cloud.iot.register_on_connect(on_connect)
|
||||||
|
cloud.iot.register_on_disconnect(on_disconnect)
|
||||||
|
|
||||||
|
|
||||||
def _map_aws_exception(err):
|
def _map_aws_exception(err):
|
||||||
"""Map AWS exception to our exceptions."""
|
"""Map AWS exception to our exceptions."""
|
||||||
ex = AWS_EXCEPTIONS.get(err.response['Error']['Code'], UnknownError)
|
ex = AWS_EXCEPTIONS.get(err.response['Error']['Code'], UnknownError)
|
||||||
@ -47,7 +87,7 @@ def _map_aws_exception(err):
|
|||||||
|
|
||||||
def register(cloud, email, password):
|
def register(cloud, email, password):
|
||||||
"""Register a new account."""
|
"""Register a new account."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
|
|
||||||
cognito = _cognito(cloud)
|
cognito = _cognito(cloud)
|
||||||
# Workaround for bug in Warrant. PR with fix:
|
# Workaround for bug in Warrant. PR with fix:
|
||||||
@ -55,13 +95,16 @@ def register(cloud, email, password):
|
|||||||
cognito.add_base_attributes()
|
cognito.add_base_attributes()
|
||||||
try:
|
try:
|
||||||
cognito.register(email, password)
|
cognito.register(email, password)
|
||||||
|
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def resend_email_confirm(cloud, email):
|
def resend_email_confirm(cloud, email):
|
||||||
"""Resend email confirmation."""
|
"""Resend email confirmation."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
|
|
||||||
cognito = _cognito(cloud, username=email)
|
cognito = _cognito(cloud, username=email)
|
||||||
|
|
||||||
@ -72,18 +115,23 @@ def resend_email_confirm(cloud, email):
|
|||||||
)
|
)
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def forgot_password(cloud, email):
|
def forgot_password(cloud, email):
|
||||||
"""Initialize forgotten password flow."""
|
"""Initialize forgotten password flow."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
|
|
||||||
cognito = _cognito(cloud, username=email)
|
cognito = _cognito(cloud, username=email)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cognito.initiate_forgot_password()
|
cognito.initiate_forgot_password()
|
||||||
|
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def login(cloud, email, password):
|
def login(cloud, email, password):
|
||||||
@ -97,7 +145,7 @@ def login(cloud, email, password):
|
|||||||
|
|
||||||
def check_token(cloud):
|
def check_token(cloud):
|
||||||
"""Check that the token is valid and verify if needed."""
|
"""Check that the token is valid and verify if needed."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
|
|
||||||
cognito = _cognito(
|
cognito = _cognito(
|
||||||
cloud,
|
cloud,
|
||||||
@ -109,13 +157,17 @@ def check_token(cloud):
|
|||||||
cloud.id_token = cognito.id_token
|
cloud.id_token = cognito.id_token
|
||||||
cloud.access_token = cognito.access_token
|
cloud.access_token = cognito.access_token
|
||||||
cloud.write_user_info()
|
cloud.write_user_info()
|
||||||
|
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def renew_access_token(cloud):
|
def renew_access_token(cloud):
|
||||||
"""Renew access token."""
|
"""Renew access token."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
|
|
||||||
cognito = _cognito(
|
cognito = _cognito(
|
||||||
cloud,
|
cloud,
|
||||||
@ -127,13 +179,17 @@ def renew_access_token(cloud):
|
|||||||
cloud.id_token = cognito.id_token
|
cloud.id_token = cognito.id_token
|
||||||
cloud.access_token = cognito.access_token
|
cloud.access_token = cognito.access_token
|
||||||
cloud.write_user_info()
|
cloud.write_user_info()
|
||||||
|
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def _authenticate(cloud, email, password):
|
def _authenticate(cloud, email, password):
|
||||||
"""Log in and return an authenticated Cognito instance."""
|
"""Log in and return an authenticated Cognito instance."""
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, EndpointConnectionError
|
||||||
from warrant.exceptions import ForceChangePasswordException
|
from warrant.exceptions import ForceChangePasswordException
|
||||||
|
|
||||||
assert not cloud.is_logged_in, 'Cannot login if already logged in.'
|
assert not cloud.is_logged_in, 'Cannot login if already logged in.'
|
||||||
@ -145,11 +201,14 @@ def _authenticate(cloud, email, password):
|
|||||||
return cognito
|
return cognito
|
||||||
|
|
||||||
except ForceChangePasswordException:
|
except ForceChangePasswordException:
|
||||||
raise PasswordChangeRequired
|
raise PasswordChangeRequired()
|
||||||
|
|
||||||
except ClientError as err:
|
except ClientError as err:
|
||||||
raise _map_aws_exception(err)
|
raise _map_aws_exception(err)
|
||||||
|
|
||||||
|
except EndpointConnectionError:
|
||||||
|
raise UnknownError()
|
||||||
|
|
||||||
|
|
||||||
def _cognito(cloud, **kwargs):
|
def _cognito(cloud, **kwargs):
|
||||||
"""Get the client credentials."""
|
"""Get the client credentials."""
|
||||||
|
@ -107,13 +107,16 @@ def _handle_cloud_errors(handler):
|
|||||||
result = await handler(view, request, *args, **kwargs)
|
result = await handler(view, request, *args, **kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except (auth_api.CloudError, asyncio.TimeoutError) as err:
|
except Exception as err: # pylint: disable=broad-except
|
||||||
err_info = _CLOUD_ERRORS.get(err.__class__)
|
err_info = _CLOUD_ERRORS.get(err.__class__)
|
||||||
if err_info is None:
|
if err_info is None:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unexpected error processing request for %s", request.path)
|
||||||
err_info = (502, 'Unexpected error: {}'.format(err))
|
err_info = (502, 'Unexpected error: {}'.format(err))
|
||||||
status, msg = err_info
|
status, msg = err_info
|
||||||
return view.json_message(msg, status_code=status,
|
return view.json_message(
|
||||||
message_code=err.__class__.__name__)
|
msg, status_code=status,
|
||||||
|
message_code=err.__class__.__name__.lower())
|
||||||
|
|
||||||
return error_handler
|
return error_handler
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ from homeassistant.components.alexa import smart_home as alexa
|
|||||||
from homeassistant.components.google_assistant import smart_home as ga
|
from homeassistant.components.google_assistant import smart_home as ga
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
from homeassistant.util.aiohttp import MockRequest, serialize_response
|
from homeassistant.util.aiohttp import MockRequest
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from . import auth_api
|
from . import auth_api
|
||||||
|
from . import utils
|
||||||
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
|
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
|
||||||
|
|
||||||
HANDLERS = Registry()
|
HANDLERS = Registry()
|
||||||
@ -61,12 +62,18 @@ class CloudIoT:
|
|||||||
# Local code waiting for a response
|
# Local code waiting for a response
|
||||||
self._response_handler = {}
|
self._response_handler = {}
|
||||||
self._on_connect = []
|
self._on_connect = []
|
||||||
|
self._on_disconnect = []
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def register_on_connect(self, on_connect_cb):
|
def register_on_connect(self, on_connect_cb):
|
||||||
"""Register an async on_connect callback."""
|
"""Register an async on_connect callback."""
|
||||||
self._on_connect.append(on_connect_cb)
|
self._on_connect.append(on_connect_cb)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def register_on_disconnect(self, on_disconnect_cb):
|
||||||
|
"""Register an async on_disconnect callback."""
|
||||||
|
self._on_disconnect.append(on_disconnect_cb)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self):
|
def connected(self):
|
||||||
"""Return if we're currently connected."""
|
"""Return if we're currently connected."""
|
||||||
@ -101,6 +108,17 @@ class CloudIoT:
|
|||||||
# Still adding it here to make sure we can always reconnect
|
# Still adding it here to make sure we can always reconnect
|
||||||
_LOGGER.exception("Unexpected error")
|
_LOGGER.exception("Unexpected error")
|
||||||
|
|
||||||
|
if self.state == STATE_CONNECTED and self._on_disconnect:
|
||||||
|
try:
|
||||||
|
yield from asyncio.wait([
|
||||||
|
cb() for cb in self._on_disconnect
|
||||||
|
])
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
# Safety net. This should never hit.
|
||||||
|
# Still adding it here to make sure we don't break the flow
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unexpected error in on_disconnect callbacks")
|
||||||
|
|
||||||
if self.close_requested:
|
if self.close_requested:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -191,7 +209,13 @@ class CloudIoT:
|
|||||||
self.state = STATE_CONNECTED
|
self.state = STATE_CONNECTED
|
||||||
|
|
||||||
if self._on_connect:
|
if self._on_connect:
|
||||||
|
try:
|
||||||
yield from asyncio.wait([cb() for cb in self._on_connect])
|
yield from asyncio.wait([cb() for cb in self._on_connect])
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
# Safety net. This should never hit.
|
||||||
|
# Still adding it here to make sure we don't break the flow
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unexpected error in on_connect callbacks")
|
||||||
|
|
||||||
while not client.closed:
|
while not client.closed:
|
||||||
msg = yield from client.receive()
|
msg = yield from client.receive()
|
||||||
@ -325,11 +349,6 @@ async def async_handle_cloud(hass, cloud, payload):
|
|||||||
await cloud.logout()
|
await cloud.logout()
|
||||||
_LOGGER.error("You have been logged out from Home Assistant cloud: %s",
|
_LOGGER.error("You have been logged out from Home Assistant cloud: %s",
|
||||||
payload['reason'])
|
payload['reason'])
|
||||||
elif action == 'refresh_auth':
|
|
||||||
# Refresh the auth token between now and payload['seconds']
|
|
||||||
hass.helpers.event.async_call_later(
|
|
||||||
random.randint(0, payload['seconds']),
|
|
||||||
lambda now: auth_api.check_token(cloud))
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Received unknown cloud action: %s", action)
|
_LOGGER.warning("Received unknown cloud action: %s", action)
|
||||||
|
|
||||||
@ -360,10 +379,8 @@ async def async_handle_webhook(hass, cloud, payload):
|
|||||||
response = await hass.components.webhook.async_handle_webhook(
|
response = await hass.components.webhook.async_handle_webhook(
|
||||||
found['webhook_id'], request)
|
found['webhook_id'], request)
|
||||||
|
|
||||||
response_dict = serialize_response(response)
|
response_dict = utils.aiohttp_serialize_response(response)
|
||||||
body = response_dict.get('body')
|
body = response_dict.get('body')
|
||||||
if body:
|
|
||||||
body = body.decode('utf-8')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'body': body,
|
'body': body,
|
||||||
|
13
homeassistant/components/cloud/utils.py
Normal file
13
homeassistant/components/cloud/utils.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"""Helper functions for cloud components."""
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
|
||||||
|
def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]:
|
||||||
|
"""Serialize an aiohttp response to a dictionary."""
|
||||||
|
return {
|
||||||
|
'status': response.status,
|
||||||
|
'body': response.text,
|
||||||
|
'headers': dict(response.headers),
|
||||||
|
}
|
@ -14,6 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
|
|||||||
DOMAIN = 'config'
|
DOMAIN = 'config'
|
||||||
DEPENDENCIES = ['http']
|
DEPENDENCIES = ['http']
|
||||||
SECTIONS = (
|
SECTIONS = (
|
||||||
|
'area_registry',
|
||||||
'auth',
|
'auth',
|
||||||
'auth_provider_homeassistant',
|
'auth_provider_homeassistant',
|
||||||
'automation',
|
'automation',
|
||||||
|
126
homeassistant/components/config/area_registry.py
Normal file
126
homeassistant/components/config/area_registry.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
"""HTTP views to interact with the area registry."""
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.components.websocket_api.decorators import (
|
||||||
|
async_response, require_admin)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.area_registry import async_get_registry
|
||||||
|
|
||||||
|
|
||||||
|
DEPENDENCIES = ['websocket_api']
|
||||||
|
|
||||||
|
WS_TYPE_LIST = 'config/area_registry/list'
|
||||||
|
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_LIST,
|
||||||
|
})
|
||||||
|
|
||||||
|
WS_TYPE_CREATE = 'config/area_registry/create'
|
||||||
|
SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_CREATE,
|
||||||
|
vol.Required('name'): str,
|
||||||
|
})
|
||||||
|
|
||||||
|
WS_TYPE_DELETE = 'config/area_registry/delete'
|
||||||
|
SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_DELETE,
|
||||||
|
vol.Required('area_id'): str,
|
||||||
|
})
|
||||||
|
|
||||||
|
WS_TYPE_UPDATE = 'config/area_registry/update'
|
||||||
|
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_UPDATE,
|
||||||
|
vol.Required('area_id'): str,
|
||||||
|
vol.Required('name'): str,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass):
|
||||||
|
"""Enable the Area Registry views."""
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_LIST, websocket_list_areas, SCHEMA_WS_LIST
|
||||||
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_CREATE, websocket_create_area, SCHEMA_WS_CREATE
|
||||||
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_DELETE, websocket_delete_area, SCHEMA_WS_DELETE
|
||||||
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_UPDATE, websocket_update_area, SCHEMA_WS_UPDATE
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@async_response
|
||||||
|
async def websocket_list_areas(hass, connection, msg):
|
||||||
|
"""Handle list areas command."""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
connection.send_message(websocket_api.result_message(
|
||||||
|
msg['id'], [{
|
||||||
|
'name': entry.name,
|
||||||
|
'area_id': entry.id,
|
||||||
|
} for entry in registry.async_list_areas()]
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
|
@async_response
|
||||||
|
async def websocket_create_area(hass, connection, msg):
|
||||||
|
"""Create area command."""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
try:
|
||||||
|
entry = registry.async_create(msg['name'])
|
||||||
|
except ValueError as err:
|
||||||
|
connection.send_message(websocket_api.error_message(
|
||||||
|
msg['id'], 'invalid_info', str(err)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
connection.send_message(websocket_api.result_message(
|
||||||
|
msg['id'], _entry_dict(entry)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
|
@async_response
|
||||||
|
async def websocket_delete_area(hass, connection, msg):
|
||||||
|
"""Delete area command."""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await registry.async_delete(msg['area_id'])
|
||||||
|
except KeyError:
|
||||||
|
connection.send_message(websocket_api.error_message(
|
||||||
|
msg['id'], 'invalid_info', "Area ID doesn't exist"
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
connection.send_message(websocket_api.result_message(
|
||||||
|
msg['id'], 'success'
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
|
@async_response
|
||||||
|
async def websocket_update_area(hass, connection, msg):
|
||||||
|
"""Handle update area websocket command."""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
entry = registry.async_update(msg['area_id'], msg['name'])
|
||||||
|
except ValueError as err:
|
||||||
|
connection.send_message(websocket_api.error_message(
|
||||||
|
msg['id'], 'invalid_info', str(err)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
connection.send_message(websocket_api.result_message(
|
||||||
|
msg['id'], _entry_dict(entry)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _entry_dict(entry):
|
||||||
|
"""Convert entry to API format."""
|
||||||
|
return {
|
||||||
|
'area_id': entry.id,
|
||||||
|
'name': entry.name
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
"""HTTP views to interact with the device registry."""
|
"""HTTP views to interact with the device registry."""
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import async_get_registry
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.components.websocket_api.decorators import (
|
||||||
|
async_response, require_admin)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.device_registry import async_get_registry
|
||||||
|
|
||||||
DEPENDENCIES = ['websocket_api']
|
DEPENDENCIES = ['websocket_api']
|
||||||
|
|
||||||
@ -11,22 +14,53 @@ SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|||||||
vol.Required('type'): WS_TYPE_LIST,
|
vol.Required('type'): WS_TYPE_LIST,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
WS_TYPE_UPDATE = 'config/device_registry/update'
|
||||||
|
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_UPDATE,
|
||||||
|
vol.Required('device_id'): str,
|
||||||
|
vol.Optional('area_id'): vol.Any(str, None),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass):
|
async def async_setup(hass):
|
||||||
"""Enable the Entity Registry views."""
|
"""Enable the Device Registry views."""
|
||||||
hass.components.websocket_api.async_register_command(
|
hass.components.websocket_api.async_register_command(
|
||||||
WS_TYPE_LIST, websocket_list_devices,
|
WS_TYPE_LIST, websocket_list_devices,
|
||||||
SCHEMA_WS_LIST
|
SCHEMA_WS_LIST
|
||||||
)
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_UPDATE, websocket_update_device, SCHEMA_WS_UPDATE
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.async_response
|
@async_response
|
||||||
async def websocket_list_devices(hass, connection, msg):
|
async def websocket_list_devices(hass, connection, msg):
|
||||||
"""Handle list devices command."""
|
"""Handle list devices command."""
|
||||||
registry = await async_get_registry(hass)
|
registry = await async_get_registry(hass)
|
||||||
connection.send_message(websocket_api.result_message(
|
connection.send_message(websocket_api.result_message(
|
||||||
msg['id'], [{
|
msg['id'], [_entry_dict(entry) for entry in registry.devices.values()]
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
|
@async_response
|
||||||
|
async def websocket_update_device(hass, connection, msg):
|
||||||
|
"""Handle update area websocket command."""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
|
||||||
|
entry = registry.async_update_device(
|
||||||
|
msg['device_id'], area_id=msg['area_id'])
|
||||||
|
|
||||||
|
connection.send_message(websocket_api.result_message(
|
||||||
|
msg['id'], _entry_dict(entry)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _entry_dict(entry):
|
||||||
|
"""Convert entry to API format."""
|
||||||
|
return {
|
||||||
'config_entries': list(entry.config_entries),
|
'config_entries': list(entry.config_entries),
|
||||||
'connections': list(entry.connections),
|
'connections': list(entry.connections),
|
||||||
'manufacturer': entry.manufacturer,
|
'manufacturer': entry.manufacturer,
|
||||||
@ -35,5 +69,5 @@ async def websocket_list_devices(hass, connection, msg):
|
|||||||
'sw_version': entry.sw_version,
|
'sw_version': entry.sw_version,
|
||||||
'id': entry.id,
|
'id': entry.id,
|
||||||
'hub_device_id': entry.hub_device_id,
|
'hub_device_id': entry.hub_device_id,
|
||||||
} for entry in registry.devices.values()]
|
'area_id': entry.area_id,
|
||||||
))
|
}
|
||||||
|
@ -5,7 +5,8 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.helpers.entity_registry import async_get_registry
|
from homeassistant.helpers.entity_registry import async_get_registry
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
||||||
from homeassistant.components.websocket_api.decorators import async_response
|
from homeassistant.components.websocket_api.decorators import (
|
||||||
|
async_response, require_admin)
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
DEPENDENCIES = ['websocket_api']
|
DEPENDENCIES = ['websocket_api']
|
||||||
@ -30,6 +31,12 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|||||||
vol.Optional('new_entity_id'): str,
|
vol.Optional('new_entity_id'): str,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
WS_TYPE_REMOVE = 'config/entity_registry/remove'
|
||||||
|
SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_REMOVE,
|
||||||
|
vol.Required('entity_id'): cv.entity_id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass):
|
async def async_setup(hass):
|
||||||
"""Enable the Entity Registry views."""
|
"""Enable the Entity Registry views."""
|
||||||
@ -45,6 +52,10 @@ async def async_setup(hass):
|
|||||||
WS_TYPE_UPDATE, websocket_update_entity,
|
WS_TYPE_UPDATE, websocket_update_entity,
|
||||||
SCHEMA_WS_UPDATE
|
SCHEMA_WS_UPDATE
|
||||||
)
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_REMOVE, websocket_remove_entity,
|
||||||
|
SCHEMA_WS_REMOVE
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -56,14 +67,7 @@ async def websocket_list_entities(hass, connection, msg):
|
|||||||
"""
|
"""
|
||||||
registry = await async_get_registry(hass)
|
registry = await async_get_registry(hass)
|
||||||
connection.send_message(websocket_api.result_message(
|
connection.send_message(websocket_api.result_message(
|
||||||
msg['id'], [{
|
msg['id'], [_entry_dict(entry) for entry in registry.entities.values()]
|
||||||
'config_entry_id': entry.config_entry_id,
|
|
||||||
'device_id': entry.device_id,
|
|
||||||
'disabled_by': entry.disabled_by,
|
|
||||||
'entity_id': entry.entity_id,
|
|
||||||
'name': entry.name,
|
|
||||||
'platform': entry.platform,
|
|
||||||
} for entry in registry.entities.values()]
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@ -86,6 +90,7 @@ async def websocket_get_entity(hass, connection, msg):
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
@async_response
|
@async_response
|
||||||
async def websocket_update_entity(hass, connection, msg):
|
async def websocket_update_entity(hass, connection, msg):
|
||||||
"""Handle update entity websocket command.
|
"""Handle update entity websocket command.
|
||||||
@ -125,10 +130,32 @@ async def websocket_update_entity(hass, connection, msg):
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin
|
||||||
|
@async_response
|
||||||
|
async def websocket_remove_entity(hass, connection, msg):
|
||||||
|
"""Handle remove entity websocket command.
|
||||||
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
registry = await async_get_registry(hass)
|
||||||
|
|
||||||
|
if msg['entity_id'] not in registry.entities:
|
||||||
|
connection.send_message(websocket_api.error_message(
|
||||||
|
msg['id'], ERR_NOT_FOUND, 'Entity not found'))
|
||||||
|
return
|
||||||
|
|
||||||
|
registry.async_remove(msg['entity_id'])
|
||||||
|
connection.send_message(websocket_api.result_message(msg['id']))
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _entry_dict(entry):
|
def _entry_dict(entry):
|
||||||
"""Convert entry to API format."""
|
"""Convert entry to API format."""
|
||||||
return {
|
return {
|
||||||
|
'config_entry_id': entry.config_entry_id,
|
||||||
|
'device_id': entry.device_id,
|
||||||
|
'disabled_by': entry.disabled_by,
|
||||||
'entity_id': entry.entity_id,
|
'entity_id': entry.entity_id,
|
||||||
'name': entry.name
|
'name': entry.name,
|
||||||
|
'platform': entry.platform,
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
|
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
|
||||||
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
|
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
|
||||||
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
|
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
|
||||||
STATE_CLOSED, STATE_UNKNOWN, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
|
STATE_CLOSED, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class CoverDevice(Entity):
|
|||||||
closed = self.is_closed
|
closed = self.is_closed
|
||||||
|
|
||||||
if closed is None:
|
if closed is None:
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
return STATE_CLOSED if closed else STATE_OPEN
|
return STATE_CLOSED if closed else STATE_OPEN
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
|
|||||||
from homeassistant.helpers.event import track_utc_time_change
|
from homeassistant.helpers.event import track_utc_time_change
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME,
|
CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME,
|
||||||
STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, CONF_COVERS)
|
STATE_CLOSED, STATE_OPEN, CONF_COVERS)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class GaradgetCover(CoverDevice):
|
|||||||
self.obtained_token = False
|
self.obtained_token = False
|
||||||
self._username = args['username']
|
self._username = args['username']
|
||||||
self._password = args['password']
|
self._password = args['password']
|
||||||
self._state = STATE_UNKNOWN
|
self._state = None
|
||||||
self.time_in_state = None
|
self.time_in_state = None
|
||||||
self.signal = None
|
self.signal = None
|
||||||
self.sensor = None
|
self.sensor = None
|
||||||
@ -156,7 +156,7 @@ class GaradgetCover(CoverDevice):
|
|||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Return if the cover is closed."""
|
"""Return if the cover is closed."""
|
||||||
if self._state == STATE_UNKNOWN:
|
if self._state is None:
|
||||||
return None
|
return None
|
||||||
return self._state == STATE_CLOSED
|
return self._state == STATE_CLOSED
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ class GaradgetCover(CoverDevice):
|
|||||||
try:
|
try:
|
||||||
status = self._get_variable('doorStatus')
|
status = self._get_variable('doorStatus')
|
||||||
_LOGGER.debug("Current Status: %s", status['status'])
|
_LOGGER.debug("Current Status: %s", status['status'])
|
||||||
self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN)
|
self._state = STATES_MAP.get(status['status'], None)
|
||||||
self.time_in_state = status['time']
|
self.time_in_state = status['time']
|
||||||
self.signal = status['signal']
|
self.signal = status['signal']
|
||||||
self.sensor = status['sensor']
|
self.sensor = status['sensor']
|
||||||
|
70
homeassistant/components/cover/homematicip_cloud.py
Normal file
70
homeassistant/components/cover/homematicip_cloud.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Support for HomematicIP Cloud cover devices.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/cover.homematicip_cloud/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.cover import (
|
||||||
|
ATTR_POSITION, CoverDevice)
|
||||||
|
from homeassistant.components.homematicip_cloud import (
|
||||||
|
HMIPC_HAPID, HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['homematicip_cloud']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(
|
||||||
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Set up the HomematicIP Cloud cover devices."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the HomematicIP cover from a config entry."""
|
||||||
|
from homematicip.aio.device import AsyncFullFlushShutter
|
||||||
|
|
||||||
|
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
|
||||||
|
devices = []
|
||||||
|
for device in home.devices:
|
||||||
|
if isinstance(device, AsyncFullFlushShutter):
|
||||||
|
devices.append(HomematicipCoverShutter(home, device))
|
||||||
|
|
||||||
|
if devices:
|
||||||
|
async_add_entities(devices)
|
||||||
|
|
||||||
|
|
||||||
|
class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice):
|
||||||
|
"""Representation of a HomematicIP Cloud cover device."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_cover_position(self):
|
||||||
|
"""Return current position of cover."""
|
||||||
|
return int(self._device.shutterLevel * 100)
|
||||||
|
|
||||||
|
async def async_set_cover_position(self, **kwargs):
|
||||||
|
"""Move the cover to a specific position."""
|
||||||
|
position = kwargs[ATTR_POSITION]
|
||||||
|
level = position / 100.0
|
||||||
|
await self._device.set_shutter_level(level)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closed(self):
|
||||||
|
"""Return if the cover is closed."""
|
||||||
|
if self._device.shutterLevel is not None:
|
||||||
|
return self._device.shutterLevel == 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def async_open_cover(self, **kwargs):
|
||||||
|
"""Open the cover."""
|
||||||
|
await self._device.set_shutter_level(1)
|
||||||
|
|
||||||
|
async def async_close_cover(self, **kwargs):
|
||||||
|
"""Close the cover."""
|
||||||
|
await self._device.set_shutter_level(0)
|
||||||
|
|
||||||
|
async def async_stop_cover(self, **kwargs):
|
||||||
|
"""Stop the device if in motion."""
|
||||||
|
await self._device.set_shutter_stop()
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
STATE_OPEN, STATE_OPENING)
|
STATE_OPEN, STATE_OPENING)
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['pymyq==1.0.0']
|
REQUIREMENTS = ['pymyq==1.1.0']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MYQ_TO_HASS = {
|
MYQ_TO_HASS = {
|
||||||
|
@ -4,8 +4,7 @@ Support for Wink Covers.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/cover.wink/
|
https://home-assistant.io/components/cover.wink/
|
||||||
"""
|
"""
|
||||||
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \
|
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
|
||||||
ATTR_POSITION
|
|
||||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||||
|
|
||||||
DEPENDENCIES = ['wink']
|
DEPENDENCIES = ['wink']
|
||||||
@ -54,7 +53,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
|
|||||||
"""Return the current position of cover shutter."""
|
"""Return the current position of cover shutter."""
|
||||||
if self.wink.state() is not None:
|
if self.wink.state() is not None:
|
||||||
return int(self.wink.state()*100)
|
return int(self.wink.state()*100)
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
|
19
homeassistant/components/daikin/.translations/zh-Hans.json
Normal file
19
homeassistant/components/daikin/.translations/zh-Hans.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210",
|
||||||
|
"device_fail": "\u521b\u5efa\u8bbe\u5907\u65f6\u51fa\u73b0\u610f\u5916\u9519\u8bef\u3002",
|
||||||
|
"device_timeout": "\u8fde\u63a5\u8bbe\u5907\u8d85\u65f6\u3002"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "\u4e3b\u673a"
|
||||||
|
},
|
||||||
|
"description": "\u8f93\u5165\u60a8\u7684 Daikin \u7a7a\u8c03\u7684IP\u5730\u5740\u3002",
|
||||||
|
"title": "\u914d\u7f6e Daikin \u7a7a\u8c03"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Daikin \u7a7a\u8c03"
|
||||||
|
}
|
||||||
|
}
|
80
homeassistant/components/danfoss_air/__init__.py
Normal file
80
homeassistant/components/danfoss_air/__init__.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
Support for Danfoss Air HRV.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/danfoss_air/
|
||||||
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST
|
||||||
|
from homeassistant.helpers import discovery
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
|
REQUIREMENTS = ['pydanfossair==0.0.6']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor']
|
||||||
|
DOMAIN = 'danfoss_air'
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Set up the Danfoss Air component."""
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = DanfossAir(conf[CONF_HOST])
|
||||||
|
|
||||||
|
for platform in DANFOSS_AIR_PLATFORMS:
|
||||||
|
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DanfossAir:
|
||||||
|
"""Handle all communication with Danfoss Air CCM unit."""
|
||||||
|
|
||||||
|
def __init__(self, host):
|
||||||
|
"""Initialize the Danfoss Air CCM connection."""
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
from pydanfossair.danfossclient import DanfossClient
|
||||||
|
|
||||||
|
self._client = DanfossClient(host)
|
||||||
|
|
||||||
|
def get_value(self, item):
|
||||||
|
"""Get value for sensor."""
|
||||||
|
return self._data.get(item)
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
"""Use the data from Danfoss Air API."""
|
||||||
|
_LOGGER.debug("Fetching data from Danfoss Air CCM module")
|
||||||
|
from pydanfossair.commands import ReadCommand
|
||||||
|
self._data[ReadCommand.exhaustTemperature] \
|
||||||
|
= self._client.command(ReadCommand.exhaustTemperature)
|
||||||
|
self._data[ReadCommand.outdoorTemperature] \
|
||||||
|
= self._client.command(ReadCommand.outdoorTemperature)
|
||||||
|
self._data[ReadCommand.supplyTemperature] \
|
||||||
|
= self._client.command(ReadCommand.supplyTemperature)
|
||||||
|
self._data[ReadCommand.extractTemperature] \
|
||||||
|
= self._client.command(ReadCommand.extractTemperature)
|
||||||
|
self._data[ReadCommand.humidity] \
|
||||||
|
= round(self._client.command(ReadCommand.humidity), 2)
|
||||||
|
self._data[ReadCommand.filterPercent] \
|
||||||
|
= round(self._client.command(ReadCommand.filterPercent), 2)
|
||||||
|
self._data[ReadCommand.bypass] \
|
||||||
|
= self._client.command(ReadCommand.bypass)
|
||||||
|
|
||||||
|
_LOGGER.debug("Done fetching data from Danfoss Air CCM module")
|
56
homeassistant/components/danfoss_air/binary_sensor.py
Normal file
56
homeassistant/components/danfoss_air/binary_sensor.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""
|
||||||
|
Support for the for Danfoss Air HRV binary sensor platform.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/binary_sensor.danfoss_air/
|
||||||
|
"""
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.danfoss_air import DOMAIN \
|
||||||
|
as DANFOSS_AIR_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the available Danfoss Air sensors etc."""
|
||||||
|
from pydanfossair.commands import ReadCommand
|
||||||
|
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||||
|
|
||||||
|
sensors = [["Danfoss Air Bypass Active", ReadCommand.bypass]]
|
||||||
|
|
||||||
|
dev = []
|
||||||
|
|
||||||
|
for sensor in sensors:
|
||||||
|
dev.append(DanfossAirBinarySensor(data, sensor[0], sensor[1]))
|
||||||
|
|
||||||
|
add_entities(dev, True)
|
||||||
|
|
||||||
|
|
||||||
|
class DanfossAirBinarySensor(BinarySensorDevice):
|
||||||
|
"""Representation of a Danfoss Air binary sensor."""
|
||||||
|
|
||||||
|
def __init__(self, data, name, sensor_type):
|
||||||
|
"""Initialize the Danfoss Air binary sensor."""
|
||||||
|
self._data = data
|
||||||
|
self._name = name
|
||||||
|
self._state = None
|
||||||
|
self._type = sensor_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Type of device class."""
|
||||||
|
return "opening"
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Fetch new state data for the sensor."""
|
||||||
|
self._data.update()
|
||||||
|
|
||||||
|
self._state = self._data.get_value(self._type)
|
76
homeassistant/components/danfoss_air/sensor.py
Normal file
76
homeassistant/components/danfoss_air/sensor.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
Support for the for Danfoss Air HRV sensor platform.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.danfoss_air/
|
||||||
|
"""
|
||||||
|
from homeassistant.components.danfoss_air import DOMAIN \
|
||||||
|
as DANFOSS_AIR_DOMAIN
|
||||||
|
from homeassistant.const import TEMP_CELSIUS
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the available Danfoss Air sensors etc."""
|
||||||
|
from pydanfossair.commands import ReadCommand
|
||||||
|
|
||||||
|
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||||
|
|
||||||
|
sensors = [
|
||||||
|
["Danfoss Air Exhaust Temperature", TEMP_CELSIUS,
|
||||||
|
ReadCommand.exhaustTemperature],
|
||||||
|
["Danfoss Air Outdoor Temperature", TEMP_CELSIUS,
|
||||||
|
ReadCommand.outdoorTemperature],
|
||||||
|
["Danfoss Air Supply Temperature", TEMP_CELSIUS,
|
||||||
|
ReadCommand.supplyTemperature],
|
||||||
|
["Danfoss Air Extract Temperature", TEMP_CELSIUS,
|
||||||
|
ReadCommand.extractTemperature],
|
||||||
|
["Danfoss Air Remaining Filter", '%',
|
||||||
|
ReadCommand.filterPercent],
|
||||||
|
["Danfoss Air Humidity", '%',
|
||||||
|
ReadCommand.humidity]
|
||||||
|
]
|
||||||
|
|
||||||
|
dev = []
|
||||||
|
|
||||||
|
for sensor in sensors:
|
||||||
|
dev.append(DanfossAir(data, sensor[0], sensor[1], sensor[2]))
|
||||||
|
|
||||||
|
add_entities(dev, True)
|
||||||
|
|
||||||
|
|
||||||
|
class DanfossAir(Entity):
|
||||||
|
"""Representation of a Sensor."""
|
||||||
|
|
||||||
|
def __init__(self, data, name, sensor_unit, sensor_type):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self._data = data
|
||||||
|
self._name = name
|
||||||
|
self._state = None
|
||||||
|
self._type = sensor_type
|
||||||
|
self._unit = sensor_unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update the new state of the sensor.
|
||||||
|
|
||||||
|
This is done through the DanfossAir object that does the actual
|
||||||
|
communication with the Air CCM.
|
||||||
|
"""
|
||||||
|
self._data.update()
|
||||||
|
|
||||||
|
self._state = self._data.get_value(self._type)
|
@ -17,7 +17,7 @@
|
|||||||
"title": "Define deCONZ gateway"
|
"title": "Define deCONZ gateway"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button",
|
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button",
|
||||||
"title": "Link with deCONZ"
|
"title": "Link with deCONZ"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"init": {
|
"init": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"port": "Port (warto\u015b\u0107 domy\u015blna: \"80\")"
|
"port": "Port"
|
||||||
},
|
},
|
||||||
"title": "Zdefiniuj bramk\u0119 deCONZ"
|
"title": "Zdefiniuj bramk\u0119 deCONZ"
|
||||||
},
|
},
|
||||||
@ -28,6 +28,6 @@
|
|||||||
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
|
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "Brama deCONZ Zigbee"
|
||||||
}
|
}
|
||||||
}
|
}
|
107
homeassistant/components/device_tracker/ee_brightbox.py
Normal file
107
homeassistant/components/device_tracker/ee_brightbox.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"""
|
||||||
|
Support for EE Brightbox router.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/device_tracker.ee_brightbox/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.device_tracker import (
|
||||||
|
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
REQUIREMENTS = ['eebrightbox==0.0.4']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_VERSION = 'version'
|
||||||
|
|
||||||
|
CONF_DEFAULT_IP = '192.168.1.1'
|
||||||
|
CONF_DEFAULT_USERNAME = 'admin'
|
||||||
|
CONF_DEFAULT_VERSION = 2
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_VERSION, default=CONF_DEFAULT_VERSION): cv.positive_int,
|
||||||
|
vol.Required(CONF_HOST, default=CONF_DEFAULT_IP): cv.string,
|
||||||
|
vol.Required(CONF_USERNAME, default=CONF_DEFAULT_USERNAME): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
"""Return a router scanner instance."""
|
||||||
|
scanner = EEBrightBoxScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.check_config() else None
|
||||||
|
|
||||||
|
|
||||||
|
class EEBrightBoxScanner(DeviceScanner):
|
||||||
|
"""Scan EE Brightbox router."""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
"""Initialise the scanner."""
|
||||||
|
self.config = config
|
||||||
|
self.devices = {}
|
||||||
|
|
||||||
|
def check_config(self):
|
||||||
|
"""Check if provided configuration and credentials are correct."""
|
||||||
|
from eebrightbox import EEBrightBox, EEBrightBoxException
|
||||||
|
|
||||||
|
try:
|
||||||
|
with EEBrightBox(self.config) as ee_brightbox:
|
||||||
|
return bool(ee_brightbox.get_devices())
|
||||||
|
except EEBrightBoxException:
|
||||||
|
_LOGGER.exception("Failed to connect to the router")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
"""Scan for devices."""
|
||||||
|
from eebrightbox import EEBrightBox
|
||||||
|
|
||||||
|
with EEBrightBox(self.config) as ee_brightbox:
|
||||||
|
self.devices = {d['mac']: d for d in ee_brightbox.get_devices()}
|
||||||
|
|
||||||
|
macs = [d['mac'] for d in self.devices.values() if d['activity_ip']]
|
||||||
|
|
||||||
|
_LOGGER.debug('Scan devices %s', macs)
|
||||||
|
|
||||||
|
return macs
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
"""Get the name of a device from hostname."""
|
||||||
|
if device in self.devices:
|
||||||
|
return self.devices[device]['hostname'] or None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_extra_attributes(self, device):
|
||||||
|
"""
|
||||||
|
Get the extra attributes of a device.
|
||||||
|
|
||||||
|
Extra attributes include:
|
||||||
|
- ip
|
||||||
|
- mac
|
||||||
|
- port - ethX or wifiX
|
||||||
|
- last_active
|
||||||
|
"""
|
||||||
|
port_map = {
|
||||||
|
'wl1': 'wifi5Ghz',
|
||||||
|
'wl0': 'wifi2.4Ghz',
|
||||||
|
'eth0': 'eth0',
|
||||||
|
'eth1': 'eth1',
|
||||||
|
'eth2': 'eth2',
|
||||||
|
'eth3': 'eth3',
|
||||||
|
}
|
||||||
|
|
||||||
|
if device in self.devices:
|
||||||
|
return {
|
||||||
|
'ip': self.devices[device]['ip'],
|
||||||
|
'mac': self.devices[device]['mac'],
|
||||||
|
'port': port_map[self.devices[device]['port']],
|
||||||
|
'last_active': self.devices[device]['time_last_active'],
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
@ -1,32 +0,0 @@
|
|||||||
"""
|
|
||||||
Support for the GPSLogger platform.
|
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
|
||||||
https://home-assistant.io/components/device_tracker.gpslogger/
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from homeassistant.components.gpslogger import TRACKER_UPDATE
|
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEPENDENCIES = ['gpslogger']
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType,
|
|
||||||
async_see, discovery_info=None):
|
|
||||||
"""Set up an endpoint for the GPSLogger device tracker."""
|
|
||||||
async def _set_location(device, gps_location, battery, accuracy, attrs):
|
|
||||||
"""Fire HA event to set location."""
|
|
||||||
await async_see(
|
|
||||||
dev_id=device,
|
|
||||||
gps=gps_location,
|
|
||||||
battery=battery,
|
|
||||||
gps_accuracy=accuracy,
|
|
||||||
attributes=attrs
|
|
||||||
)
|
|
||||||
|
|
||||||
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
|
|
||||||
return True
|
|
@ -1,28 +0,0 @@
|
|||||||
"""
|
|
||||||
Support for the Locative platform.
|
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
|
||||||
https://home-assistant.io/components/device_tracker.locative/
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from homeassistant.components.locative import TRACKER_UPDATE
|
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEPENDENCIES = ['locative']
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
|
||||||
"""Set up an endpoint for the Locative device tracker."""
|
|
||||||
async def _set_location(device, gps_location, location_name):
|
|
||||||
"""Fire HA event to set location."""
|
|
||||||
await async_see(
|
|
||||||
dev_id=device,
|
|
||||||
gps=gps_location,
|
|
||||||
location_name=location_name
|
|
||||||
)
|
|
||||||
|
|
||||||
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
|
|
||||||
return True
|
|
100
homeassistant/components/device_tracker/synology_srm.py
Normal file
100
homeassistant/components/device_tracker/synology_srm.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"""Device tracker for Synology SRM routers.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/device_tracker.synology_srm/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
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_USERNAME, CONF_PASSWORD,
|
||||||
|
CONF_PORT, CONF_SSL, CONF_VERIFY_SSL)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['synology-srm==0.0.3']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_USERNAME = 'admin'
|
||||||
|
DEFAULT_PORT = 8001
|
||||||
|
DEFAULT_SSL = True
|
||||||
|
DEFAULT_VERIFY_SSL = False
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||||
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
"""Validate the configuration and return Synology SRM scanner."""
|
||||||
|
scanner = SynologySrmDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class SynologySrmDeviceScanner(DeviceScanner):
|
||||||
|
"""This class scans for devices connected to a Synology SRM router."""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
"""Initialize the scanner."""
|
||||||
|
import synology_srm
|
||||||
|
|
||||||
|
self.client = synology_srm.Client(
|
||||||
|
host=config[CONF_HOST],
|
||||||
|
port=config[CONF_PORT],
|
||||||
|
username=config[CONF_USERNAME],
|
||||||
|
password=config[CONF_PASSWORD],
|
||||||
|
https=config[CONF_SSL]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not config[CONF_VERIFY_SSL]:
|
||||||
|
self.client.http.disable_https_verify()
|
||||||
|
|
||||||
|
self.last_results = []
|
||||||
|
self.success_init = self._update_info()
|
||||||
|
|
||||||
|
_LOGGER.info("Synology SRM scanner initialized")
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
"""Scan for new devices and return a list with found device IDs."""
|
||||||
|
self._update_info()
|
||||||
|
|
||||||
|
return [device['mac'] for device in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
"""Return the name of the given device or None if we don't know."""
|
||||||
|
filter_named = [result['hostname'] for result in self.last_results if
|
||||||
|
result['mac'] == device]
|
||||||
|
|
||||||
|
if filter_named:
|
||||||
|
return filter_named[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _update_info(self):
|
||||||
|
"""Check the router for connected devices."""
|
||||||
|
_LOGGER.debug("Scanning for connected devices")
|
||||||
|
|
||||||
|
devices = self.client.mesh.network_wifidevice()
|
||||||
|
last_results = []
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
last_results.append({
|
||||||
|
'mac': device['mac'],
|
||||||
|
'hostname': device['hostname']
|
||||||
|
})
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Found %d device(s) connected to the router",
|
||||||
|
len(devices)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.last_results = last_results
|
||||||
|
return True
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistent dostopen prek interneta.",
|
"not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistant dostopen prek interneta.",
|
||||||
"one_instance_allowed": "Potrebna je samo ena instanca."
|
"one_instance_allowed": "Potrebna je samo ena instanca."
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila."
|
"default": "Za po\u0161iljanje dogodkov Home Assistant-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Dialogflow \u6d88\u606f\u3002",
|
||||||
|
"one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b"
|
||||||
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Dialogflow \u7684 Webhook \u96c6\u6210]({dialogflow_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002"
|
"default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Dialogflow \u7684 Webhook \u96c6\u6210]({dialogflow_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
"description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Dialogflow \u5417?",
|
||||||
"title": "\u8bbe\u7f6e Dialogflow Webhook"
|
"title": "\u8bbe\u7f6e Dialogflow Webhook"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
79
homeassistant/components/dovado/__init__.py
Normal file
79
homeassistant/components/dovado/__init__.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Support for Dovado router.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/dovado/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT,
|
||||||
|
DEVICE_DEFAULT_NAME)
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['dovado==0.4.1']
|
||||||
|
|
||||||
|
DOMAIN = 'dovado'
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_PORT): cv.port,
|
||||||
|
})
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Set up the Dovado component."""
|
||||||
|
import dovado
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = DovadoData(
|
||||||
|
dovado.Dovado(
|
||||||
|
config[CONF_USERNAME],
|
||||||
|
config[CONF_PASSWORD],
|
||||||
|
config.get(CONF_HOST),
|
||||||
|
config.get(CONF_PORT)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DovadoData:
|
||||||
|
"""Maintains a connection to the router."""
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
"""Set up a new Dovado connection."""
|
||||||
|
self._client = client
|
||||||
|
self.state = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Name of the router."""
|
||||||
|
return self.state.get("product name", DEVICE_DEFAULT_NAME)
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
"""Update device state."""
|
||||||
|
try:
|
||||||
|
self.state = self._client.state or {}
|
||||||
|
if not self.state:
|
||||||
|
return False
|
||||||
|
self.state.update(
|
||||||
|
connected=self.state.get("modem status") == "CONNECTED")
|
||||||
|
_LOGGER.debug("Received: %s", self.state)
|
||||||
|
return True
|
||||||
|
except OSError as error:
|
||||||
|
_LOGGER.warning("Could not contact the router: %s", error)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
"""Dovado client instance."""
|
||||||
|
return self._client
|
38
homeassistant/components/dovado/notify.py
Normal file
38
homeassistant/components/dovado/notify.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
Support for SMS notifications from the Dovado router.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/notify.dovado/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN
|
||||||
|
from homeassistant.components.notify import BaseNotificationService, \
|
||||||
|
ATTR_TARGET
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['dovado']
|
||||||
|
|
||||||
|
|
||||||
|
def get_service(hass, config, discovery_info=None):
|
||||||
|
"""Get the Dovado Router SMS notification service."""
|
||||||
|
return DovadoSMSNotificationService(hass.data[DOVADO_DOMAIN].client)
|
||||||
|
|
||||||
|
|
||||||
|
class DovadoSMSNotificationService(BaseNotificationService):
|
||||||
|
"""Implement the notification service for the Dovado SMS component."""
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
"""Initialize the service."""
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
def send_message(self, message, **kwargs):
|
||||||
|
"""Send SMS to the specified target phone number."""
|
||||||
|
target = kwargs.get(ATTR_TARGET)
|
||||||
|
|
||||||
|
if not target:
|
||||||
|
_LOGGER.error("One target is required")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._client.send_sms(target, message)
|
116
homeassistant/components/dovado/sensor.py
Normal file
116
homeassistant/components/dovado/sensor.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""
|
||||||
|
Support for sensors from the Dovado router.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.dovado/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN
|
||||||
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
|
from homeassistant.const import CONF_SENSORS
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['dovado']
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||||
|
|
||||||
|
SENSOR_UPLOAD = 'upload'
|
||||||
|
SENSOR_DOWNLOAD = 'download'
|
||||||
|
SENSOR_SIGNAL = 'signal'
|
||||||
|
SENSOR_NETWORK = 'network'
|
||||||
|
SENSOR_SMS_UNREAD = 'sms'
|
||||||
|
|
||||||
|
SENSORS = {
|
||||||
|
SENSOR_NETWORK: ('signal strength', 'Network', None,
|
||||||
|
'mdi:access-point-network'),
|
||||||
|
SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%',
|
||||||
|
'mdi:signal'),
|
||||||
|
SENSOR_SMS_UNREAD: ('sms unread', 'SMS unread', '',
|
||||||
|
'mdi:message-text-outline'),
|
||||||
|
SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB',
|
||||||
|
'mdi:cloud-upload'),
|
||||||
|
SENSOR_DOWNLOAD: ('traffic modem rx', 'Received', 'GB',
|
||||||
|
'mdi:cloud-download'),
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_SENSORS): vol.All(
|
||||||
|
cv.ensure_list, [vol.In(SENSORS)]
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the Dovado sensor platform."""
|
||||||
|
dovado = hass.data[DOVADO_DOMAIN]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for sensor in config[CONF_SENSORS]:
|
||||||
|
entities.append(DovadoSensor(dovado, sensor))
|
||||||
|
|
||||||
|
add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class DovadoSensor(Entity):
|
||||||
|
"""Representation of a Dovado sensor."""
|
||||||
|
|
||||||
|
def __init__(self, data, sensor):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self._data = data
|
||||||
|
self._sensor = sensor
|
||||||
|
self._state = self._compute_state()
|
||||||
|
|
||||||
|
def _compute_state(self):
|
||||||
|
state = self._data.state.get(SENSORS[self._sensor][0])
|
||||||
|
if self._sensor == SENSOR_NETWORK:
|
||||||
|
match = re.search(r"\((.+)\)", state)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
if self._sensor == SENSOR_SIGNAL:
|
||||||
|
try:
|
||||||
|
return int(state.split()[0])
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
if self._sensor == SENSOR_SMS_UNREAD:
|
||||||
|
return int(state)
|
||||||
|
if self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
|
||||||
|
return round(float(state) / 1e6, 1)
|
||||||
|
return state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update sensor values."""
|
||||||
|
self._data.update()
|
||||||
|
self._state = self._compute_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return "{} {}".format(self._data.name, SENSORS[self._sensor][1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the sensor state."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the icon for the sensor."""
|
||||||
|
return SENSORS[self._sensor][3]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return SENSORS[self._sensor][2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes."""
|
||||||
|
return {k: v for k, v in self._data.state.items()
|
||||||
|
if k not in ['date', 'time']}
|
98
homeassistant/components/ecoal_boiler.py
Normal file
98
homeassistant/components/ecoal_boiler.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
Component to control ecoal/esterownik.pl coal/wood boiler controller.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/ecoal_boiler/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
|
||||||
|
CONF_MONITORED_CONDITIONS, CONF_SENSORS,
|
||||||
|
CONF_SWITCHES)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.discovery import load_platform
|
||||||
|
|
||||||
|
REQUIREMENTS = ['ecoaliface==0.4.0']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "ecoal_boiler"
|
||||||
|
DATA_ECOAL_BOILER = 'data_' + DOMAIN
|
||||||
|
|
||||||
|
DEFAULT_USERNAME = "admin"
|
||||||
|
DEFAULT_PASSWORD = "admin"
|
||||||
|
|
||||||
|
|
||||||
|
# Available pump ids with assigned HA names
|
||||||
|
# Available as switches
|
||||||
|
AVAILABLE_PUMPS = {
|
||||||
|
"central_heating_pump": "Central heating pump",
|
||||||
|
"central_heating_pump2": "Central heating pump2",
|
||||||
|
"domestic_hot_water_pump": "Domestic hot water pump",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Available temp sensor ids with assigned HA names
|
||||||
|
# Available as sensors
|
||||||
|
AVAILABLE_SENSORS = {
|
||||||
|
"outdoor_temp": 'Outdoor temperature',
|
||||||
|
"indoor_temp": 'Indoor temperature',
|
||||||
|
"indoor2_temp": 'Indoor temperature 2',
|
||||||
|
"domestic_hot_water_temp": 'Domestic hot water temperature',
|
||||||
|
"target_domestic_hot_water_temp": 'Target hot water temperature',
|
||||||
|
"feedwater_in_temp": 'Feedwater input temperature',
|
||||||
|
"feedwater_out_temp": 'Feedwater output temperature',
|
||||||
|
"target_feedwater_temp": 'Target feedwater temperature',
|
||||||
|
"fuel_feeder_temp": 'Fuel feeder temperature',
|
||||||
|
"exhaust_temp": 'Exhaust temperature',
|
||||||
|
}
|
||||||
|
|
||||||
|
SWITCH_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_PUMPS)):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(AVAILABLE_PUMPS)])
|
||||||
|
})
|
||||||
|
|
||||||
|
SENSOR_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_SENSORS)):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(AVAILABLE_SENSORS)])
|
||||||
|
})
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_USERNAME,
|
||||||
|
default=DEFAULT_USERNAME): cv.string,
|
||||||
|
vol.Optional(CONF_PASSWORD,
|
||||||
|
default=DEFAULT_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA,
|
||||||
|
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
|
||||||
|
})
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, hass_config):
|
||||||
|
"""Set up global ECoalController instance same for sensors and switches."""
|
||||||
|
from ecoaliface.simple import ECoalController
|
||||||
|
|
||||||
|
conf = hass_config[DOMAIN]
|
||||||
|
host = conf[CONF_HOST]
|
||||||
|
username = conf[CONF_USERNAME]
|
||||||
|
passwd = conf[CONF_PASSWORD]
|
||||||
|
# Creating ECoalController instance makes HTTP request to controller.
|
||||||
|
ecoal_contr = ECoalController(host, username, passwd)
|
||||||
|
if ecoal_contr.version is None:
|
||||||
|
# Wrong credentials nor network config
|
||||||
|
_LOGGER.error("Unable to read controller status from %s@%s"
|
||||||
|
" (wrong host/credentials)", username, host, )
|
||||||
|
return False
|
||||||
|
_LOGGER.debug("Detected controller version: %r @%s",
|
||||||
|
ecoal_contr.version, host, )
|
||||||
|
hass.data[DATA_ECOAL_BOILER] = ecoal_contr
|
||||||
|
# Setup switches
|
||||||
|
switches = conf[CONF_SWITCHES][CONF_MONITORED_CONDITIONS]
|
||||||
|
load_platform(hass, 'switch', DOMAIN, switches, hass_config)
|
||||||
|
# Setup temp sensors
|
||||||
|
sensors = conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS]
|
||||||
|
load_platform(hass, 'sensor', DOMAIN, sensors, hass_config)
|
||||||
|
return True
|
@ -22,7 +22,7 @@ from homeassistant.components.http import real_ip
|
|||||||
|
|
||||||
from .hue_api import (
|
from .hue_api import (
|
||||||
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
|
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
|
||||||
HueOneLightChangeView, HueGroupView)
|
HueOneLightChangeView, HueGroupView, HueAllGroupsStateView)
|
||||||
from .upnp import DescriptionXmlView, UPNPResponderThread
|
from .upnp import DescriptionXmlView, UPNPResponderThread
|
||||||
|
|
||||||
DOMAIN = 'emulated_hue'
|
DOMAIN = 'emulated_hue'
|
||||||
@ -105,6 +105,7 @@ async def async_setup(hass, yaml_config):
|
|||||||
HueAllLightsStateView(config).register(app, app.router)
|
HueAllLightsStateView(config).register(app, app.router)
|
||||||
HueOneLightStateView(config).register(app, app.router)
|
HueOneLightStateView(config).register(app, app.router)
|
||||||
HueOneLightChangeView(config).register(app, app.router)
|
HueOneLightChangeView(config).register(app, app.router)
|
||||||
|
HueAllGroupsStateView(config).register(app, app.router)
|
||||||
HueGroupView(config).register(app, app.router)
|
HueGroupView(config).register(app, app.router)
|
||||||
|
|
||||||
upnp_listener = UPNPResponderThread(
|
upnp_listener = UPNPResponderThread(
|
||||||
|
@ -56,6 +56,28 @@ class HueUsernameView(HomeAssistantView):
|
|||||||
return self.json([{'success': {'username': '12345678901234567890'}}])
|
return self.json([{'success': {'username': '12345678901234567890'}}])
|
||||||
|
|
||||||
|
|
||||||
|
class HueAllGroupsStateView(HomeAssistantView):
|
||||||
|
"""Group handler."""
|
||||||
|
|
||||||
|
url = '/api/{username}/groups'
|
||||||
|
name = 'emulated_hue:all_groups:state'
|
||||||
|
requires_auth = False
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
"""Initialize the instance of the view."""
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def get(self, request, username):
|
||||||
|
"""Process a request to make the Brilliant Lightpad work."""
|
||||||
|
if not is_local(request[KEY_REAL_IP]):
|
||||||
|
return self.json_message('only local IPs allowed',
|
||||||
|
HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
|
return self.json({
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class HueGroupView(HomeAssistantView):
|
class HueGroupView(HomeAssistantView):
|
||||||
"""Group handler to get Logitech Pop working."""
|
"""Group handler to get Logitech Pop working."""
|
||||||
|
|
||||||
|
17
homeassistant/components/emulated_roku/.translations/es.json
Normal file
17
homeassistant/components/emulated_roku/.translations/es.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "El nombre ya existe"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host_ip": "IP del host",
|
||||||
|
"listen_port": "Puerto de escucha",
|
||||||
|
"name": "Nombre"
|
||||||
|
},
|
||||||
|
"title": "Definir la configuraci\u00f3n del servidor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
"host_ip": "\ud638\uc2a4\ud2b8 IP",
|
"host_ip": "\ud638\uc2a4\ud2b8 IP",
|
||||||
"listen_port": "\uc218\uc2e0 \ud3ec\ud2b8",
|
"listen_port": "\uc218\uc2e0 \ud3ec\ud2b8",
|
||||||
"name": "\uc774\ub984",
|
"name": "\uc774\ub984",
|
||||||
"upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ubc14\uc778\ub4dc (\ucc38/\uac70\uc9d3)"
|
"upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ud560\ub2f9 (\ucc38/\uac70\uc9d3)"
|
||||||
},
|
},
|
||||||
"title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758"
|
"title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758"
|
||||||
}
|
}
|
||||||
|
21
homeassistant/components/emulated_roku/.translations/lb.json
Normal file
21
homeassistant/components/emulated_roku/.translations/lb.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "Numm g\u00ebtt et schonn"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"advertise_ip": "IP annonc\u00e9ieren",
|
||||||
|
"advertise_port": "Port annonc\u00e9ieren",
|
||||||
|
"host_ip": "IP vum Apparat",
|
||||||
|
"listen_port": "Port lauschteren",
|
||||||
|
"name": "Numm",
|
||||||
|
"upnp_bind_multicast": "Multicast abannen (Richteg/Falsch)"
|
||||||
|
},
|
||||||
|
"title": "Server Konfiguratioun d\u00e9fin\u00e9ieren"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "EmulatedRoku"
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/emulated_roku/.translations/pl.json
Normal file
21
homeassistant/components/emulated_roku/.translations/pl.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "Nazwa ju\u017c istnieje"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"advertise_ip": "IP rozg\u0142aszania",
|
||||||
|
"advertise_port": "Port rozg\u0142aszania",
|
||||||
|
"host_ip": "IP hosta",
|
||||||
|
"listen_port": "Port nas\u0142uchu",
|
||||||
|
"name": "Nazwa",
|
||||||
|
"upnp_bind_multicast": "Powi\u0105\u017c multicast (prawda/fa\u0142sz)"
|
||||||
|
},
|
||||||
|
"title": "Zdefiniuj konfiguracj\u0119 serwera"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "EmulatedRoku"
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/emulated_roku/.translations/sl.json
Normal file
21
homeassistant/components/emulated_roku/.translations/sl.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "Ime \u017ee obstaja"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"advertise_ip": "Advertise IP",
|
||||||
|
"advertise_port": "Advertise port",
|
||||||
|
"host_ip": "IP gostitelja",
|
||||||
|
"listen_port": "Vrata naprave",
|
||||||
|
"name": "Ime",
|
||||||
|
"upnp_bind_multicast": "Vezava multicasta (True / False)"
|
||||||
|
},
|
||||||
|
"title": "Dolo\u010dite konfiguracijo stre\u017enika"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "EmulatedRoku"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "\u540d\u79f0\u5df2\u5b58\u5728"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host_ip": "\u4e3b\u673aIP",
|
||||||
|
"listen_port": "\u76d1\u542c\u7aef\u53e3",
|
||||||
|
"name": "\u59d3\u540d"
|
||||||
|
},
|
||||||
|
"title": "\u5b9a\u4e49\u670d\u52a1\u5668\u914d\u7f6e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"advertise_ip": "\u5ee3\u64ad\u901a\u8a0a\u57e0",
|
||||||
|
"advertise_port": "\u5ee3\u64ad\u901a\u8a0a\u57e0",
|
||||||
|
"host_ip": "\u4e3b\u6a5f IP",
|
||||||
|
"listen_port": "\u76e3\u807d\u901a\u8a0a\u57e0",
|
||||||
|
"name": "\u540d\u7a31",
|
||||||
|
"upnp_bind_multicast": "\u7d81\u5b9a\u7fa4\u64ad\uff08Multicast\uff09True/False"
|
||||||
|
},
|
||||||
|
"title": "\u5b9a\u7fa9\u4f3a\u670d\u5668\u8a2d\u5b9a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "EmulatedRoku"
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ from .const import (
|
|||||||
CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT,
|
CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT,
|
||||||
CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN)
|
CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN)
|
||||||
|
|
||||||
REQUIREMENTS = ['emulated_roku==0.1.7']
|
REQUIREMENTS = ['emulated_roku==0.1.8']
|
||||||
|
|
||||||
SERVER_CONFIG_SCHEMA = vol.Schema({
|
SERVER_CONFIG_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_NAME): cv.string,
|
vol.Required(CONF_NAME): cv.string,
|
||||||
|
@ -146,19 +146,19 @@ async def async_setup(hass, config):
|
|||||||
@callback
|
@callback
|
||||||
def zones_updated_callback(data):
|
def zones_updated_callback(data):
|
||||||
"""Handle zone timer updates."""
|
"""Handle zone timer updates."""
|
||||||
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
|
_LOGGER.debug("Envisalink sent a zone update event. Updating zones...")
|
||||||
async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
|
async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def alarm_data_updated_callback(data):
|
def alarm_data_updated_callback(data):
|
||||||
"""Handle non-alarm based info updates."""
|
"""Handle non-alarm based info updates."""
|
||||||
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
|
_LOGGER.debug("Envisalink sent new alarm info. Updating alarms...")
|
||||||
async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
|
async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def partition_updated_callback(data):
|
def partition_updated_callback(data):
|
||||||
"""Handle partition changes thrown by evl (including alarms)."""
|
"""Handle partition changes thrown by evl (including alarms)."""
|
||||||
_LOGGER.info("The envisalink sent a partition update event")
|
_LOGGER.debug("The envisalink sent a partition update event")
|
||||||
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
|
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
25
homeassistant/components/esphome/.translations/es.json
Normal file
25
homeassistant/components/esphome/.translations/es.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "ESP ya est\u00e1 configurado"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_password": "\u00a1Contrase\u00f1a incorrecta!"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"authenticate": {
|
||||||
|
"data": {
|
||||||
|
"password": "Contrase\u00f1a"
|
||||||
|
},
|
||||||
|
"description": "Escribe la contrase\u00f1a que hayas establecido en tu configuraci\u00f3n.",
|
||||||
|
"title": "Escribe la contrase\u00f1a"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Puerto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
"already_configured": "ESP \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
"already_configured": "ESP \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api :' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.",
|
"connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.",
|
||||||
"invalid_password": "\uc798\ubabb\ub41c \ube44\ubc00\ubc88\ud638",
|
"invalid_password": "\uc798\ubabb\ub41c \ube44\ubc00\ubc88\ud638",
|
||||||
"resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c (https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694"
|
"resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c (https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694"
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "ESP jest ju\u017c skonfigurowany"
|
"already_configured": "ESP jest ju\u017c skonfigurowane"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 \"api:\".",
|
"connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 'api:'.",
|
||||||
"invalid_password": "Nieprawid\u0142owe has\u0142o!",
|
"invalid_password": "Nieprawid\u0142owe has\u0142o!",
|
||||||
"resolve_error": "Nie mo\u017cna rozpozna\u0107 adresu ESP. Je\u015bli ten b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, nale\u017cy ustawi\u0107 statyczny adres IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
|
"resolve_error": "Nie mo\u017cna rozpozna\u0107 adresu ESP. Je\u015bli ten b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, nale\u017cy ustawi\u0107 statyczny adres IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
|
||||||
},
|
},
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"port": "Port"
|
"port": "Port"
|
||||||
},
|
},
|
||||||
"description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome] (https://esphomelib.com/) w\u0119z\u0142a.",
|
"description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.",
|
||||||
"title": "ESPHome"
|
"title": "ESPHome"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
27
homeassistant/components/esphome/.translations/zh-Hans.json
Normal file
27
homeassistant/components/esphome/.translations/zh-Hans.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"connection_error": "\u65e0\u6cd5\u8fde\u63a5\u5230ESP\u3002\u8bf7\u786e\u4fdd\u60a8\u7684YAML\u6587\u4ef6\u5305\u542b'api:'\u884c\u3002",
|
||||||
|
"invalid_password": "\u65e0\u6548\u7684\u5bc6\u7801\uff01",
|
||||||
|
"resolve_error": "\u65e0\u6cd5\u89e3\u6790ESP\u7684\u5730\u5740\u3002\u5982\u679c\u6b64\u9519\u8bef\u4ecd\u7136\u5b58\u5728\uff0c\u8bf7\u8bbe\u7f6e\u9759\u6001IP\u5730\u5740\uff1ahttps://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"authenticate": {
|
||||||
|
"data": {
|
||||||
|
"password": "\u5bc6\u7801"
|
||||||
|
},
|
||||||
|
"description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002",
|
||||||
|
"title": "\u8f93\u5165\u5bc6\u7801"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "\u4e3b\u673a",
|
||||||
|
"port": "\u7aef\u53e3"
|
||||||
|
},
|
||||||
|
"description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002",
|
||||||
|
"title": "ESPHome"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "ESPHome"
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group
|
||||||
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE,
|
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE,
|
||||||
SERVICE_TURN_OFF, ATTR_ENTITY_ID,
|
SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
STATE_UNKNOWN)
|
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
@ -94,7 +93,7 @@ def is_on(hass, entity_id: str = None) -> bool:
|
|||||||
"""Return if the fans are on based on the statemachine."""
|
"""Return if the fans are on based on the statemachine."""
|
||||||
entity_id = entity_id or ENTITY_ID_ALL_FANS
|
entity_id = entity_id or ENTITY_ID_ALL_FANS
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, STATE_UNKNOWN]
|
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, None]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config: dict):
|
async def async_setup(hass, config: dict):
|
||||||
@ -199,7 +198,7 @@ class FanEntity(ToggleEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if the entity is on."""
|
"""Return true if the entity is on."""
|
||||||
return self.speed not in [SPEED_OFF, STATE_UNKNOWN]
|
return self.speed not in [SPEED_OFF, None]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed(self) -> str:
|
def speed(self) -> str:
|
||||||
|
@ -11,7 +11,6 @@ from homeassistant.components.comfoconnect import (
|
|||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
||||||
SUPPORT_SET_SPEED)
|
SUPPORT_SET_SPEED)
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
|
||||||
from homeassistant.helpers.dispatcher import (dispatcher_connect)
|
from homeassistant.helpers.dispatcher import (dispatcher_connect)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -79,7 +78,7 @@ class ComfoConnectFan(FanEntity):
|
|||||||
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
|
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
|
||||||
return SPEED_MAPPING[speed]
|
return SPEED_MAPPING[speed]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed_list(self):
|
def speed_list(self):
|
||||||
|
@ -7,7 +7,7 @@ https://home-assistant.io/components/fan.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, STATE_UNKNOWN, SUPPORT_DIRECTION,
|
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION,
|
||||||
SUPPORT_SET_SPEED, FanEntity)
|
SUPPORT_SET_SPEED, FanEntity)
|
||||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ class WinkFanDevice(WinkDevice, FanEntity):
|
|||||||
return SPEED_MEDIUM
|
return SPEED_MEDIUM
|
||||||
if SPEED_HIGH == current_wink_speed:
|
if SPEED_HIGH == current_wink_speed:
|
||||||
return SPEED_HIGH
|
return SPEED_HIGH
|
||||||
return STATE_UNKNOWN
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_direction(self):
|
def current_direction(self):
|
||||||
|
@ -38,7 +38,7 @@ MODEL_AIRPURIFIER_MA1 = 'zhimi.airpurifier.ma1'
|
|||||||
MODEL_AIRPURIFIER_MA2 = 'zhimi.airpurifier.ma2'
|
MODEL_AIRPURIFIER_MA2 = 'zhimi.airpurifier.ma2'
|
||||||
MODEL_AIRPURIFIER_SA1 = 'zhimi.airpurifier.sa1'
|
MODEL_AIRPURIFIER_SA1 = 'zhimi.airpurifier.sa1'
|
||||||
MODEL_AIRPURIFIER_SA2 = 'zhimi.airpurifier.sa2'
|
MODEL_AIRPURIFIER_SA2 = 'zhimi.airpurifier.sa2'
|
||||||
MODEL_AIRPURIFIER_MC1 = 'zhimi.airpurifier.mc1'
|
MODEL_AIRPURIFIER_2S = 'zhimi.airpurifier.mc1'
|
||||||
|
|
||||||
MODEL_AIRHUMIDIFIER_V1 = 'zhimi.humidifier.v1'
|
MODEL_AIRHUMIDIFIER_V1 = 'zhimi.humidifier.v1'
|
||||||
MODEL_AIRHUMIDIFIER_CA = 'zhimi.humidifier.ca1'
|
MODEL_AIRHUMIDIFIER_CA = 'zhimi.humidifier.ca1'
|
||||||
@ -62,7 +62,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
MODEL_AIRPURIFIER_MA2,
|
MODEL_AIRPURIFIER_MA2,
|
||||||
MODEL_AIRPURIFIER_SA1,
|
MODEL_AIRPURIFIER_SA1,
|
||||||
MODEL_AIRPURIFIER_SA2,
|
MODEL_AIRPURIFIER_SA2,
|
||||||
MODEL_AIRPURIFIER_MC1,
|
MODEL_AIRPURIFIER_2S,
|
||||||
MODEL_AIRHUMIDIFIER_V1,
|
MODEL_AIRHUMIDIFIER_V1,
|
||||||
MODEL_AIRHUMIDIFIER_CA,
|
MODEL_AIRHUMIDIFIER_CA,
|
||||||
MODEL_AIRFRESH_VA2,
|
MODEL_AIRFRESH_VA2,
|
||||||
@ -175,6 +175,15 @@ AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7 = {
|
|||||||
ATTR_VOLUME: 'volume',
|
ATTR_VOLUME: 'volume',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S = {
|
||||||
|
**AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON,
|
||||||
|
ATTR_BUZZER: 'buzzer',
|
||||||
|
ATTR_FILTER_RFID_PRODUCT_ID: 'filter_rfid_product_id',
|
||||||
|
ATTR_FILTER_RFID_TAG: 'filter_rfid_tag',
|
||||||
|
ATTR_FILTER_TYPE: 'filter_type',
|
||||||
|
ATTR_ILLUMINANCE: 'illuminance',
|
||||||
|
}
|
||||||
|
|
||||||
AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = {
|
AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = {
|
||||||
# Common set isn't used here. It's a very basic version of the device.
|
# Common set isn't used here. It's a very basic version of the device.
|
||||||
ATTR_AIR_QUALITY_INDEX: 'aqi',
|
ATTR_AIR_QUALITY_INDEX: 'aqi',
|
||||||
@ -249,6 +258,7 @@ AVAILABLE_ATTRIBUTES_AIRFRESH = {
|
|||||||
OPERATION_MODES_AIRPURIFIER = ['Auto', 'Silent', 'Favorite', 'Idle']
|
OPERATION_MODES_AIRPURIFIER = ['Auto', 'Silent', 'Favorite', 'Idle']
|
||||||
OPERATION_MODES_AIRPURIFIER_PRO = ['Auto', 'Silent', 'Favorite']
|
OPERATION_MODES_AIRPURIFIER_PRO = ['Auto', 'Silent', 'Favorite']
|
||||||
OPERATION_MODES_AIRPURIFIER_PRO_V7 = OPERATION_MODES_AIRPURIFIER_PRO
|
OPERATION_MODES_AIRPURIFIER_PRO_V7 = OPERATION_MODES_AIRPURIFIER_PRO
|
||||||
|
OPERATION_MODES_AIRPURIFIER_2S = ['Auto', 'Silent', 'Favorite']
|
||||||
OPERATION_MODES_AIRPURIFIER_V3 = ['Auto', 'Silent', 'Favorite', 'Idle',
|
OPERATION_MODES_AIRPURIFIER_V3 = ['Auto', 'Silent', 'Favorite', 'Idle',
|
||||||
'Medium', 'High', 'Strong']
|
'Medium', 'High', 'Strong']
|
||||||
OPERATION_MODES_AIRFRESH = ['Auto', 'Silent', 'Interval', 'Low',
|
OPERATION_MODES_AIRFRESH = ['Auto', 'Silent', 'Interval', 'Low',
|
||||||
@ -289,6 +299,11 @@ FEATURE_FLAGS_AIRPURIFIER_PRO_V7 = (FEATURE_SET_CHILD_LOCK |
|
|||||||
FEATURE_SET_FAVORITE_LEVEL |
|
FEATURE_SET_FAVORITE_LEVEL |
|
||||||
FEATURE_SET_VOLUME)
|
FEATURE_SET_VOLUME)
|
||||||
|
|
||||||
|
FEATURE_FLAGS_AIRPURIFIER_2S = (FEATURE_SET_BUZZER |
|
||||||
|
FEATURE_SET_CHILD_LOCK |
|
||||||
|
FEATURE_SET_LED |
|
||||||
|
FEATURE_SET_FAVORITE_LEVEL)
|
||||||
|
|
||||||
FEATURE_FLAGS_AIRPURIFIER_V3 = (FEATURE_SET_BUZZER |
|
FEATURE_FLAGS_AIRPURIFIER_V3 = (FEATURE_SET_BUZZER |
|
||||||
FEATURE_SET_CHILD_LOCK |
|
FEATURE_SET_CHILD_LOCK |
|
||||||
FEATURE_SET_LED)
|
FEATURE_SET_LED)
|
||||||
@ -619,6 +634,10 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
self._available_attributes = \
|
self._available_attributes = \
|
||||||
AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
|
AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
|
||||||
self._speed_list = OPERATION_MODES_AIRPURIFIER_PRO_V7
|
self._speed_list = OPERATION_MODES_AIRPURIFIER_PRO_V7
|
||||||
|
elif self._model == MODEL_AIRPURIFIER_2S:
|
||||||
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S
|
||||||
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S
|
||||||
|
self._speed_list = OPERATION_MODES_AIRPURIFIER_2S
|
||||||
elif self._model == MODEL_AIRPURIFIER_V3:
|
elif self._model == MODEL_AIRPURIFIER_V3:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3
|
||||||
|
@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/ffmpeg/
|
https://home-assistant.io/components/ffmpeg/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['ha-ffmpeg==1.9']
|
REQUIREMENTS = ['ha-ffmpeg==1.11']
|
||||||
|
|
||||||
DOMAIN = 'ffmpeg'
|
DOMAIN = 'ffmpeg'
|
||||||
|
|
||||||
@ -60,6 +61,8 @@ async def async_setup(hass, config):
|
|||||||
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY)
|
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await manager.async_get_version()
|
||||||
|
|
||||||
# Register service
|
# Register service
|
||||||
async def async_service_handle(service):
|
async def async_service_handle(service):
|
||||||
"""Handle service ffmpeg process."""
|
"""Handle service ffmpeg process."""
|
||||||
@ -96,12 +99,37 @@ class FFmpegManager:
|
|||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._bin = ffmpeg_bin
|
self._bin = ffmpeg_bin
|
||||||
|
self._version = None
|
||||||
|
self._major_version = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def binary(self):
|
def binary(self):
|
||||||
"""Return ffmpeg binary from config."""
|
"""Return ffmpeg binary from config."""
|
||||||
return self._bin
|
return self._bin
|
||||||
|
|
||||||
|
async def async_get_version(self):
|
||||||
|
"""Return ffmpeg version."""
|
||||||
|
from haffmpeg.tools import FFVersion
|
||||||
|
|
||||||
|
ffversion = FFVersion(self._bin, self.hass.loop)
|
||||||
|
self._version = await ffversion.get_version()
|
||||||
|
|
||||||
|
self._major_version = None
|
||||||
|
if self._version is not None:
|
||||||
|
result = re.search(r"(\d+)\.", self._version)
|
||||||
|
if result is not None:
|
||||||
|
self._major_version = int(result.group(1))
|
||||||
|
|
||||||
|
return self._version, self._major_version
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ffmpeg_stream_content_type(self):
|
||||||
|
"""Return HTTP content type for ffmpeg stream."""
|
||||||
|
if self._major_version is not None and self._major_version > 3:
|
||||||
|
return 'multipart/x-mixed-replace;boundary=ffmpeg'
|
||||||
|
|
||||||
|
return 'multipart/x-mixed-replace;boundary=ffserver'
|
||||||
|
|
||||||
|
|
||||||
class FFmpegBase(Entity):
|
class FFmpegBase(Entity):
|
||||||
"""Interface object for FFmpeg."""
|
"""Interface object for FFmpeg."""
|
||||||
|
@ -12,7 +12,8 @@ import aiohttp
|
|||||||
import async_timeout
|
import async_timeout
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN)
|
from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN,
|
||||||
|
CONF_UPDATE_INTERVAL)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -24,8 +25,6 @@ DEFAULT_INTERVAL = timedelta(minutes=10)
|
|||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php'
|
UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php'
|
||||||
|
|
||||||
CONF_UPDATE_INTERVAL = 'update_interval'
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
vol.Exclusive(CONF_URL, DOMAIN): cv.string,
|
vol.Exclusive(CONF_URL, DOMAIN): cv.string,
|
||||||
|
@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
REQUIREMENTS = ['pyfritzhome==0.4.0']
|
REQUIREMENTS = ['pyfritzhome==0.4.0']
|
||||||
|
|
||||||
SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch']
|
SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch', 'sensor']
|
||||||
|
|
||||||
DOMAIN = 'fritzbox'
|
DOMAIN = 'fritzbox'
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.helpers.translation import async_get_translations
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
REQUIREMENTS = ['home-assistant-frontend==20190121.1']
|
REQUIREMENTS = ['home-assistant-frontend==20190203.0']
|
||||||
|
|
||||||
DOMAIN = 'frontend'
|
DOMAIN = 'frontend'
|
||||||
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
|
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user