commit
88bc3033d3
12
.coveragerc
12
.coveragerc
|
@ -136,6 +136,7 @@ omit =
|
|||
homeassistant/components/device_tracker/tplink.py
|
||||
homeassistant/components/device_tracker/traccar.py
|
||||
homeassistant/components/device_tracker/trackr.py
|
||||
homeassistant/components/device_tracker/ubee.py
|
||||
homeassistant/components/device_tracker/ubus.py
|
||||
homeassistant/components/digital_ocean/*
|
||||
homeassistant/components/dominos/*
|
||||
|
@ -204,6 +205,7 @@ omit =
|
|||
homeassistant/components/insteon/*
|
||||
homeassistant/components/ios/*
|
||||
homeassistant/components/iota/*
|
||||
homeassistant/components/iperf3/*
|
||||
homeassistant/components/isy994/*
|
||||
homeassistant/components/joaoapps_join/*
|
||||
homeassistant/components/juicenet/*
|
||||
|
@ -317,6 +319,8 @@ omit =
|
|||
homeassistant/components/media_player/yamaha_musiccast.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
homeassistant/components/media_player/ziggo_mediabox_xl.py
|
||||
homeassistant/components/meteo_france/*
|
||||
homeassistant/components/mobile_app/*
|
||||
homeassistant/components/mochad/*
|
||||
homeassistant/components/modbus/*
|
||||
homeassistant/components/mychevy/*
|
||||
|
@ -326,6 +330,7 @@ omit =
|
|||
homeassistant/components/nest/*
|
||||
homeassistant/components/netatmo/*
|
||||
homeassistant/components/netgear_lte/*
|
||||
homeassistant/components/nissan_leaf/*
|
||||
homeassistant/components/notify/aws_lambda.py
|
||||
homeassistant/components/notify/aws_sns.py
|
||||
homeassistant/components/notify/aws_sqs.py
|
||||
|
@ -374,10 +379,13 @@ omit =
|
|||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/openuv/binary_sensor.py
|
||||
homeassistant/components/openuv/sensor.py
|
||||
homeassistant/components/owlet/*
|
||||
homeassistant/components/pilight/*
|
||||
homeassistant/components/plum_lightpad/*
|
||||
homeassistant/components/point/*
|
||||
homeassistant/components/prometheus/*
|
||||
homeassistant/components/ps4/__init__.py
|
||||
homeassistant/components/ps4/media_player.py
|
||||
homeassistant/components/qwikswitch/*
|
||||
homeassistant/components/rachio/*
|
||||
homeassistant/components/rainbird/*
|
||||
|
@ -388,6 +396,7 @@ omit =
|
|||
homeassistant/components/rainmachine/switch.py
|
||||
homeassistant/components/raspihats/*
|
||||
homeassistant/components/raspyrfm/*
|
||||
homeassistant/components/reddit/*
|
||||
homeassistant/components/remember_the_milk/__init__.py
|
||||
homeassistant/components/remote/harmony.py
|
||||
homeassistant/components/remote/itach.py
|
||||
|
@ -470,7 +479,6 @@ omit =
|
|||
homeassistant/components/sensor/imap_email_content.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/influxdb.py
|
||||
homeassistant/components/sensor/iperf3.py
|
||||
homeassistant/components/sensor/irish_rail_transport.py
|
||||
homeassistant/components/sensor/kwb.py
|
||||
homeassistant/components/sensor/lacrosse.py
|
||||
|
@ -482,7 +490,6 @@ omit =
|
|||
homeassistant/components/sensor/loopenergy.py
|
||||
homeassistant/components/sensor/lyft.py
|
||||
homeassistant/components/sensor/magicseaweed.py
|
||||
homeassistant/components/sensor/meteo_france.py
|
||||
homeassistant/components/sensor/metoffice.py
|
||||
homeassistant/components/sensor/miflora.py
|
||||
homeassistant/components/sensor/mitemp_bt.py
|
||||
|
@ -609,6 +616,7 @@ omit =
|
|||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/rpi_rf.py
|
||||
homeassistant/components/switch/snmp.py
|
||||
homeassistant/components/switch/sony_projector.py
|
||||
homeassistant/components/switch/switchbot.py
|
||||
homeassistant/components/switch/switchmate.py
|
||||
homeassistant/components/switch/telnet.py
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
|
||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
||||
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
|
||||
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
|
||||
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
|
||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
|
||||
|
|
|
@ -8,6 +8,7 @@ about: Create a report to help us improve
|
|||
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
|
||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
||||
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
|
||||
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
|
||||
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
|
||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
|
||||
|
|
|
@ -34,7 +34,7 @@ cache:
|
|||
- $HOME/.cache/pip
|
||||
install: pip install -U tox coveralls
|
||||
language: python
|
||||
script: travis_wait 30 tox --develop
|
||||
script: travis_wait 40 tox --develop
|
||||
services:
|
||||
- docker
|
||||
before_deploy:
|
||||
|
|
76
CODEOWNERS
76
CODEOWNERS
|
@ -28,7 +28,7 @@ homeassistant/components/panel_iframe/* @home-assistant/core
|
|||
homeassistant/components/onboarding/* @home-assistant/core
|
||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||
homeassistant/components/scene/__init__.py @home-assistant/core
|
||||
homeassistant/components/scene/hass.py @home-assistant/core
|
||||
homeassistant/components/scene/homeassistant.py @home-assistant/core
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/sun/* @home-assistant/core
|
||||
|
@ -47,7 +47,6 @@ homeassistant/components/*/zwave.py @home-assistant/z-wave
|
|||
homeassistant/components/hassio/* @home-assistant/hassio
|
||||
|
||||
# Individual platforms
|
||||
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
|
||||
homeassistant/components/binary_sensor/hikvision.py @mezz64
|
||||
homeassistant/components/binary_sensor/threshold.py @fabaff
|
||||
|
@ -68,10 +67,8 @@ homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan
|
|||
homeassistant/components/device_tracker/tile.py @bachya
|
||||
homeassistant/components/device_tracker/traccar.py @ludeeus
|
||||
homeassistant/components/device_tracker/bt_smarthub.py @jxwolstenholme
|
||||
homeassistant/components/history_graph/* @andrey-git
|
||||
homeassistant/components/influx/* @fabaff
|
||||
homeassistant/components/device_tracker/synology_srm.py @aerialls
|
||||
homeassistant/components/light/lifx_legacy.py @amelchio
|
||||
homeassistant/components/light/tplink.py @rytilahti
|
||||
homeassistant/components/light/yeelight.py @rytilahti
|
||||
homeassistant/components/light/yeelightsunflower.py @lindsaymarkward
|
||||
homeassistant/components/lock/nello.py @pschmitt
|
||||
|
@ -82,20 +79,15 @@ homeassistant/components/media_player/liveboxplaytv.py @pschmitt
|
|||
homeassistant/components/media_player/mediaroom.py @dgomes
|
||||
homeassistant/components/media_player/monoprice.py @etsinko
|
||||
homeassistant/components/media_player/mpd.py @fabaff
|
||||
homeassistant/components/media_player/sonos.py @amelchio
|
||||
homeassistant/components/media_player/xiaomi_tv.py @fattdev
|
||||
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
|
||||
homeassistant/components/no_ip/* @fabaff
|
||||
homeassistant/components/notify/file.py @fabaff
|
||||
homeassistant/components/notify/flock.py @fabaff
|
||||
homeassistant/components/notify/instapush.py @fabaff
|
||||
homeassistant/components/notify/mastodon.py @fabaff
|
||||
homeassistant/components/notify/smtp.py @fabaff
|
||||
homeassistant/components/notify/syslog.py @fabaff
|
||||
homeassistant/components/notify/xmpp.py @fabaff
|
||||
homeassistant/components/notify/yessssms.py @flowolf
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/remote/harmony.py @ehendrix23
|
||||
homeassistant/components/scene/lifx_cloud.py @amelchio
|
||||
homeassistant/components/sensor/airvisual.py @bachya
|
||||
homeassistant/components/sensor/alpha_vantage.py @fabaff
|
||||
|
@ -106,11 +98,12 @@ homeassistant/components/sensor/darksky.py @fabaff
|
|||
homeassistant/components/sensor/file.py @fabaff
|
||||
homeassistant/components/sensor/filter.py @dgomes
|
||||
homeassistant/components/sensor/fixer.py @fabaff
|
||||
homeassistant/components/sensor/flunearyou.py.py @bachya
|
||||
homeassistant/components/sensor/flunearyou.py @bachya
|
||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||
homeassistant/components/sensor/gitter.py @fabaff
|
||||
homeassistant/components/sensor/glances.py @fabaff
|
||||
homeassistant/components/sensor/gpsd.py @fabaff
|
||||
homeassistant/components/sensor/integration.py @dgomes
|
||||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||
homeassistant/components/sensor/jewish_calendar.py @tsvi
|
||||
homeassistant/components/sensor/launch_library.py @ludeeus
|
||||
|
@ -135,35 +128,28 @@ homeassistant/components/sensor/statistics.py @fabaff
|
|||
homeassistant/components/sensor/swiss*.py @fabaff
|
||||
homeassistant/components/sensor/sytadin.py @gautric
|
||||
homeassistant/components/sensor/tautulli.py @ludeeus
|
||||
homeassistant/components/sensor/time_data.py @fabaff
|
||||
homeassistant/components/sensor/time_date.py @fabaff
|
||||
homeassistant/components/sensor/version.py @fabaff
|
||||
homeassistant/components/sensor/waqi.py @andrey-git
|
||||
homeassistant/components/sensor/worldclock.py @fabaff
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/spaceapi/* @fabaff
|
||||
homeassistant/components/switch/switchbot.py @danielhiversen
|
||||
homeassistant/components/switch/switchmate.py @danielhiversen
|
||||
homeassistant/components/switch/tplink.py @rytilahti
|
||||
homeassistant/components/vacuum/roomba.py @pschmitt
|
||||
homeassistant/components/weather/__init__.py @fabaff
|
||||
homeassistant/components/weather/darksky.py @fabaff
|
||||
homeassistant/components/weather/demo.py @fabaff
|
||||
homeassistant/components/weather/met.py @danielhiversen
|
||||
homeassistant/components/weather/openweathermap.py @fabaff
|
||||
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
|
||||
|
||||
# A
|
||||
homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/arduino/* @fabaff
|
||||
homeassistant/components/*/arduino.py @fabaff
|
||||
homeassistant/components/axis/* @kane610
|
||||
homeassistant/components/*/arest.py @fabaff
|
||||
homeassistant/components/*/axis.py @kane610
|
||||
|
||||
# B
|
||||
homeassistant/components/blink/* @fronzbot
|
||||
homeassistant/components/*/blink.py @fronzbot
|
||||
homeassistant/components/bmw_connected_drive/* @ChristianKuehnel
|
||||
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/*/broadlink.py @danielhiversen
|
||||
|
||||
# C
|
||||
|
@ -172,51 +158,44 @@ homeassistant/components/counter/* @fabaff
|
|||
|
||||
# D
|
||||
homeassistant/components/daikin/* @fredrike @rofrantz
|
||||
homeassistant/components/*/daikin.py @fredrike @rofrantz
|
||||
homeassistant/components/*/deconz.py @kane610
|
||||
homeassistant/components/deconz/* @kane610
|
||||
homeassistant/components/digital_ocean/* @fabaff
|
||||
homeassistant/components/*/digital_ocean.py @fabaff
|
||||
homeassistant/components/dweet/* @fabaff
|
||||
homeassistant/components/*/dweet.py @fabaff
|
||||
|
||||
# E
|
||||
homeassistant/components/ecovacs/* @OverloadUT
|
||||
homeassistant/components/*/ecovacs.py @OverloadUT
|
||||
homeassistant/components/*/edp_redy.py @abmantis
|
||||
homeassistant/components/edp_redy/* @abmantis
|
||||
homeassistant/components/eight_sleep/* @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
homeassistant/components/egardia/* @jeroenterheerdt
|
||||
homeassistant/components/esphome/*.py @OttoWinter
|
||||
|
||||
# F
|
||||
homeassistant/components/freebox/*.py @snoof85
|
||||
|
||||
# G
|
||||
homeassistant/components/googlehome/* @ludeeus
|
||||
homeassistant/components/*/googlehome.py @ludeeus
|
||||
|
||||
# H
|
||||
homeassistant/components/harmony/* @ehendrix23
|
||||
homeassistant/components/history_graph/* @andrey-git
|
||||
homeassistant/components/hive/* @Rendili @KJonline
|
||||
homeassistant/components/*/hive.py @Rendili @KJonline
|
||||
homeassistant/components/homekit/* @cdce8p
|
||||
homeassistant/components/huawei_lte/* @scop
|
||||
homeassistant/components/*/huawei_lte.py @scop
|
||||
|
||||
# I
|
||||
homeassistant/components/influx/* @fabaff
|
||||
homeassistant/components/ipma/* @dgomes
|
||||
|
||||
# K
|
||||
homeassistant/components/knx/* @Julius2342
|
||||
homeassistant/components/*/knx.py @Julius2342
|
||||
homeassistant/components/konnected/* @heythisisnate
|
||||
homeassistant/components/*/konnected.py @heythisisnate
|
||||
|
||||
# L
|
||||
homeassistant/components/lifx/* @amelchio
|
||||
homeassistant/components/*/lifx.py @amelchio
|
||||
homeassistant/components/luftdaten/* @fabaff
|
||||
homeassistant/components/*/luftdaten.py @fabaff
|
||||
|
||||
# M
|
||||
homeassistant/components/matrix/* @tinloaf
|
||||
homeassistant/components/*/matrix.py @tinloaf
|
||||
homeassistant/components/melissa/* @kennedyshead
|
||||
homeassistant/components/*/melissa.py @kennedyshead
|
||||
homeassistant/components/*/mystrom.py @fabaff
|
||||
|
@ -224,59 +203,56 @@ homeassistant/components/*/mystrom.py @fabaff
|
|||
# N
|
||||
homeassistant/components/ness_alarm/* @nickw444
|
||||
homeassistant/components/*/ness_alarm.py @nickw444
|
||||
homeassistant/components/nissan_leaf/* @filcole
|
||||
homeassistant/components/no_ip/* @fabaff
|
||||
|
||||
# O
|
||||
homeassistant/components/openuv/* @bachya
|
||||
|
||||
# P
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/point/* @fredrike
|
||||
homeassistant/components/*/point.py @fredrike
|
||||
|
||||
# Q
|
||||
homeassistant/components/qwikswitch/* @kellerza
|
||||
homeassistant/components/*/qwikswitch.py @kellerza
|
||||
|
||||
# R
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/rfxtrx/* @danielhiversen
|
||||
homeassistant/components/*/random.py @fabaff
|
||||
homeassistant/components/*/rfxtrx.py @danielhiversen
|
||||
|
||||
# S
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
homeassistant/components/sonos/* @amelchio
|
||||
homeassistant/components/spaceapi/* @fabaff
|
||||
homeassistant/components/spider/* @peternijssen
|
||||
|
||||
# T
|
||||
homeassistant/components/tahoma/* @philklei
|
||||
homeassistant/components/*/tahoma.py @philklei
|
||||
homeassistant/components/tellduslive/*.py @fredrike
|
||||
homeassistant/components/*/tellduslive.py @fredrike
|
||||
homeassistant/components/tesla/* @zabuldon
|
||||
homeassistant/components/*/tesla.py @zabuldon
|
||||
homeassistant/components/thethingsnetwork/* @fabaff
|
||||
homeassistant/components/*/thethingsnetwork.py @fabaff
|
||||
homeassistant/components/tibber/* @danielhiversen
|
||||
homeassistant/components/*/tibber.py @danielhiversen
|
||||
homeassistant/components/tplink/* @rytilahti
|
||||
homeassistant/components/tradfri/* @ggravlingen
|
||||
homeassistant/components/*/tradfri.py @ggravlingen
|
||||
homeassistant/components/toon/* @frenck
|
||||
|
||||
# U
|
||||
homeassistant/components/unifi/* @kane610
|
||||
homeassistant/components/switch/unifi.py @kane610
|
||||
homeassistant/components/upcloud/* @scop
|
||||
homeassistant/components/*/upcloud.py @scop
|
||||
homeassistant/components/utility_meter/* @dgomes
|
||||
|
||||
# V
|
||||
homeassistant/components/velux/* @Julius2342
|
||||
homeassistant/components/*/velux.py @Julius2342
|
||||
|
||||
# W
|
||||
homeassistant/components/wemo/* @sqldiablo
|
||||
homeassistant/components/*/wemo.py @sqldiablo
|
||||
|
||||
# X
|
||||
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
||||
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
||||
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
|
||||
homeassistant/components/xiaomi_miio/* @rytilahti @syssi
|
||||
|
||||
# Z
|
||||
homeassistant/components/zoneminder/* @rohankapoorcom
|
||||
|
|
|
@ -170,8 +170,7 @@ class AuthManager:
|
|||
user = await self.async_get_user_by_credentials(credentials)
|
||||
if user is None:
|
||||
raise ValueError('Unable to find the user.')
|
||||
else:
|
||||
return user
|
||||
return user
|
||||
|
||||
auth_provider = self._async_get_auth_provider(credentials)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
Sending HOTP through notify service
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Optional, List
|
||||
|
@ -90,6 +91,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
|||
self._include = config.get(CONF_INCLUDE, [])
|
||||
self._exclude = config.get(CONF_EXCLUDE, [])
|
||||
self._message_template = config[CONF_MESSAGE]
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
@property
|
||||
def input_schema(self) -> vol.Schema:
|
||||
|
@ -98,15 +100,19 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
|||
|
||||
async def _async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
data = await self._user_store.async_load()
|
||||
async with self._init_lock:
|
||||
if self._user_settings is not None:
|
||||
return
|
||||
|
||||
if data is None:
|
||||
data = {STORAGE_USERS: {}}
|
||||
data = await self._user_store.async_load()
|
||||
|
||||
self._user_settings = {
|
||||
user_id: NotifySetting(**setting)
|
||||
for user_id, setting in data.get(STORAGE_USERS, {}).items()
|
||||
}
|
||||
if data is None:
|
||||
data = {STORAGE_USERS: {}}
|
||||
|
||||
self._user_settings = {
|
||||
user_id: NotifySetting(**setting)
|
||||
for user_id, setting in data.get(STORAGE_USERS, {}).items()
|
||||
}
|
||||
|
||||
async def _async_save(self) -> None:
|
||||
"""Save data."""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Time-based One Time Password auth module."""
|
||||
import asyncio
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from typing import Any, Dict, Optional, Tuple # noqa: F401
|
||||
|
@ -68,6 +69,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
|||
self._users = None # type: Optional[Dict[str, str]]
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
@property
|
||||
def input_schema(self) -> vol.Schema:
|
||||
|
@ -76,12 +78,16 @@ class TotpAuthModule(MultiFactorAuthModule):
|
|||
|
||||
async def _async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
data = await self._user_store.async_load()
|
||||
async with self._init_lock:
|
||||
if self._users is not None:
|
||||
return
|
||||
|
||||
if data is None:
|
||||
data = {STORAGE_USERS: {}}
|
||||
data = await self._user_store.async_load()
|
||||
|
||||
self._users = data.get(STORAGE_USERS, {})
|
||||
if data is None:
|
||||
data = {STORAGE_USERS: {}}
|
||||
|
||||
self._users = data.get(STORAGE_USERS, {})
|
||||
|
||||
async def _async_save(self) -> None:
|
||||
"""Save data."""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Home Assistant auth provider."""
|
||||
import asyncio
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
@ -204,15 +205,21 @@ class HassAuthProvider(AuthProvider):
|
|||
|
||||
DEFAULT_TITLE = 'Home Assistant Local'
|
||||
|
||||
data = None
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize an Home Assistant auth provider."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.data = None # type: Optional[Data]
|
||||
self._init_lock = asyncio.Lock()
|
||||
|
||||
async def async_initialize(self) -> None:
|
||||
"""Initialize the auth provider."""
|
||||
if self.data is not None:
|
||||
return
|
||||
async with self._init_lock:
|
||||
if self.data is not None:
|
||||
return
|
||||
|
||||
self.data = Data(self.hass)
|
||||
await self.data.async_load()
|
||||
data = Data(self.hass)
|
||||
await data.async_load()
|
||||
self.data = data
|
||||
|
||||
async def async_login_flow(
|
||||
self, context: Optional[Dict]) -> LoginFlow:
|
||||
|
|
|
@ -3,18 +3,23 @@
|
|||
It shows list of users if access from trusted network.
|
||||
Abort login flow if not access from trusted network.
|
||||
"""
|
||||
from typing import Any, Dict, Optional, cast
|
||||
from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network,\
|
||||
IPv6Network
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import HomeAssistantHTTP # noqa: F401
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
IPAddress = Union[IPv4Address, IPv6Address]
|
||||
IPNetwork = Union[IPv4Network, IPv6Network]
|
||||
|
||||
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
|
||||
vol.Required('trusted_networks'): vol.All(cv.ensure_list, [ip_network])
|
||||
}, extra=vol.PREVENT_EXTRA)
|
||||
|
||||
|
||||
|
@ -35,6 +40,11 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
|||
|
||||
DEFAULT_TITLE = 'Trusted Networks'
|
||||
|
||||
@property
|
||||
def trusted_networks(self) -> List[IPNetwork]:
|
||||
"""Return trusted networks."""
|
||||
return cast(List[IPNetwork], self.config['trusted_networks'])
|
||||
|
||||
@property
|
||||
def support_mfa(self) -> bool:
|
||||
"""Trusted Networks auth provider does not support MFA."""
|
||||
|
@ -49,7 +59,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
|||
if not user.system_generated and user.is_active}
|
||||
|
||||
return TrustedNetworksLoginFlow(
|
||||
self, cast(str, context.get('ip_address')), available_users)
|
||||
self, cast(IPAddress, context.get('ip_address')), available_users)
|
||||
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Dict[str, str]) -> Credentials:
|
||||
|
@ -80,19 +90,17 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
|||
raise NotImplementedError
|
||||
|
||||
@callback
|
||||
def async_validate_access(self, ip_address: str) -> None:
|
||||
def async_validate_access(self, ip_addr: IPAddress) -> None:
|
||||
"""Make sure the access from trusted networks.
|
||||
|
||||
Raise InvalidAuthError if not.
|
||||
Raise InvalidAuthError if trusted_networks is not configured.
|
||||
"""
|
||||
hass_http = getattr(self.hass, 'http', None) # type: HomeAssistantHTTP
|
||||
|
||||
if not hass_http or not hass_http.trusted_networks:
|
||||
if not self.trusted_networks:
|
||||
raise InvalidAuthError('trusted_networks is not configured')
|
||||
|
||||
if not any(ip_address in trusted_network for trusted_network
|
||||
in hass_http.trusted_networks):
|
||||
if not any(ip_addr in trusted_network for trusted_network
|
||||
in self.trusted_networks):
|
||||
raise InvalidAuthError('Not in trusted_networks')
|
||||
|
||||
|
||||
|
@ -100,12 +108,12 @@ class TrustedNetworksLoginFlow(LoginFlow):
|
|||
"""Handler for the login flow."""
|
||||
|
||||
def __init__(self, auth_provider: TrustedNetworksAuthProvider,
|
||||
ip_address: str, available_users: Dict[str, Optional[str]]) \
|
||||
-> None:
|
||||
ip_addr: IPAddress,
|
||||
available_users: Dict[str, Optional[str]]) -> None:
|
||||
"""Initialize the login flow."""
|
||||
super().__init__(auth_provider)
|
||||
self._available_users = available_users
|
||||
self._ip_address = ip_address
|
||||
self._ip_address = ip_addr
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: Optional[Dict[str, str]] = None) \
|
||||
|
|
|
@ -85,14 +85,18 @@ async def async_from_config_dict(config: Dict[str, Any],
|
|||
async_enable_logging(hass, verbose, log_rotate_days, log_file,
|
||||
log_no_color)
|
||||
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_LOGGER.warning("Skipping pip installation of required modules. "
|
||||
"This may cause issues")
|
||||
|
||||
core_config = config.get(core.DOMAIN, {})
|
||||
has_api_password = bool((config.get('http') or {}).get('api_password'))
|
||||
has_trusted_networks = bool((config.get('http') or {})
|
||||
.get('trusted_networks'))
|
||||
has_api_password = bool(config.get('http', {}).get('api_password'))
|
||||
trusted_networks = config.get('http', {}).get('trusted_networks')
|
||||
|
||||
try:
|
||||
await conf_util.async_process_ha_core_config(
|
||||
hass, core_config, has_api_password, has_trusted_networks)
|
||||
hass, core_config, has_api_password, trusted_networks)
|
||||
except vol.Invalid as config_err:
|
||||
conf_util.async_log_exception(
|
||||
config_err, 'homeassistant', core_config, hass)
|
||||
|
@ -105,11 +109,6 @@ async def async_from_config_dict(config: Dict[str, Any],
|
|||
await hass.async_add_executor_job(
|
||||
conf_util.process_ha_config_upgrade, hass)
|
||||
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_LOGGER.warning("Skipping pip installation of required modules. "
|
||||
"This may cause issues")
|
||||
|
||||
# Make a copy because we are mutating it.
|
||||
config = OrderedDict(config)
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ REQUIREMENTS = ['abodepy==0.15.0']
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by goabode.com"
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
CONF_POLLING = 'polling'
|
||||
|
||||
DOMAIN = 'abode'
|
||||
|
@ -280,7 +281,7 @@ class AbodeDevice(Entity):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
'device_id': self._device.device_id,
|
||||
'battery_low': self._device.battery_low,
|
||||
'no_response': self._device.no_response,
|
||||
|
@ -327,7 +328,7 @@ class AbodeAutomation(Entity):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
'automation_id': self._automation.automation_id,
|
||||
'type': self._automation.type,
|
||||
'sub_type': self._automation.sub_type
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
||||
from homeassistant.components.abode import ATTRIBUTION, AbodeDevice
|
||||
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
|
@ -73,7 +73,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
'device_id': self._device.device_id,
|
||||
'battery_backup': self._device.battery,
|
||||
'cellular_backup': self._device.is_cellular,
|
||||
|
|
|
@ -4,9 +4,11 @@ import struct
|
|||
import logging
|
||||
import ctypes
|
||||
from collections import namedtuple
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import CONF_DEVICE, CONF_PORT, CONF_IP_ADDRESS, \
|
||||
EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE, CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyads==3.0.7']
|
||||
|
@ -16,18 +18,20 @@ _LOGGER = logging.getLogger(__name__)
|
|||
DATA_ADS = 'data_ads'
|
||||
|
||||
# Supported Types
|
||||
ADSTYPE_INT = 'int'
|
||||
ADSTYPE_UINT = 'uint'
|
||||
ADSTYPE_BYTE = 'byte'
|
||||
ADSTYPE_BOOL = 'bool'
|
||||
ADSTYPE_BYTE = 'byte'
|
||||
ADSTYPE_DINT = 'dint'
|
||||
ADSTYPE_INT = 'int'
|
||||
ADSTYPE_UDINT = 'udint'
|
||||
ADSTYPE_UINT = 'uint'
|
||||
|
||||
DOMAIN = 'ads'
|
||||
|
||||
CONF_ADS_FACTOR = 'factor'
|
||||
CONF_ADS_TYPE = 'adstype'
|
||||
CONF_ADS_VALUE = 'value'
|
||||
CONF_ADS_VAR = 'adsvar'
|
||||
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
|
||||
CONF_ADS_TYPE = 'adstype'
|
||||
CONF_ADS_FACTOR = 'factor'
|
||||
CONF_ADS_VALUE = 'value'
|
||||
|
||||
DOMAIN = 'ads'
|
||||
|
||||
SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name'
|
||||
|
||||
|
@ -41,7 +45,8 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
|
||||
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
|
||||
vol.Required(CONF_ADS_TYPE):
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE, ADSTYPE_BOOL]),
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE, ADSTYPE_BOOL,
|
||||
ADSTYPE_DINT, ADSTYPE_UDINT]),
|
||||
vol.Required(CONF_ADS_VALUE): vol.Coerce(int),
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
})
|
||||
|
@ -61,15 +66,19 @@ def setup(hass, config):
|
|||
AdsHub.ADS_TYPEMAP = {
|
||||
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
|
||||
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
|
||||
ADSTYPE_DINT: pyads.PLCTYPE_DINT,
|
||||
ADSTYPE_INT: pyads.PLCTYPE_INT,
|
||||
ADSTYPE_UDINT: pyads.PLCTYPE_UDINT,
|
||||
ADSTYPE_UINT: pyads.PLCTYPE_UINT,
|
||||
}
|
||||
|
||||
AdsHub.ADSError = pyads.ADSError
|
||||
AdsHub.PLCTYPE_BOOL = pyads.PLCTYPE_BOOL
|
||||
AdsHub.PLCTYPE_BYTE = pyads.PLCTYPE_BYTE
|
||||
AdsHub.PLCTYPE_DINT = pyads.PLCTYPE_DINT
|
||||
AdsHub.PLCTYPE_INT = pyads.PLCTYPE_INT
|
||||
AdsHub.PLCTYPE_UDINT = pyads.PLCTYPE_UDINT
|
||||
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT
|
||||
AdsHub.ADSError = pyads.ADSError
|
||||
|
||||
try:
|
||||
ads = AdsHub(client)
|
||||
|
@ -162,13 +171,12 @@ class AdsHub:
|
|||
hnotify, huser = self._client.add_device_notification(
|
||||
name, attr, self._device_notification_callback)
|
||||
hnotify = int(hnotify)
|
||||
self._notification_items[hnotify] = NotificationItem(
|
||||
hnotify, huser, name, plc_datatype, callback)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Added device notification %d for variable %s", hnotify, name)
|
||||
|
||||
self._notification_items[hnotify] = NotificationItem(
|
||||
hnotify, huser, name, plc_datatype, callback)
|
||||
|
||||
def _device_notification_callback(self, notification, name):
|
||||
"""Handle device notifications."""
|
||||
contents = notification.contents
|
||||
|
@ -178,9 +186,10 @@ class AdsHub:
|
|||
data = contents.data
|
||||
|
||||
try:
|
||||
notification_item = self._notification_items[hnotify]
|
||||
with self._lock:
|
||||
notification_item = self._notification_items[hnotify]
|
||||
except KeyError:
|
||||
_LOGGER.debug("Unknown device notification handle: %d", hnotify)
|
||||
_LOGGER.error("Unknown device notification handle: %d", hnotify)
|
||||
return
|
||||
|
||||
# Parse data to desired datatype
|
||||
|
@ -192,6 +201,10 @@ class AdsHub:
|
|||
value = struct.unpack('<B', bytearray(data)[:1])[0]
|
||||
elif notification_item.plc_datatype == self.PLCTYPE_UINT:
|
||||
value = struct.unpack('<H', bytearray(data)[:2])[0]
|
||||
elif notification_item.plc_datatype == self.PLCTYPE_DINT:
|
||||
value = struct.unpack('<i', bytearray(data)[:4])[0]
|
||||
elif notification_item.plc_datatype == self.PLCTYPE_UDINT:
|
||||
value = struct.unpack('<I', bytearray(data)[:4])[0]
|
||||
else:
|
||||
value = bytearray(data)
|
||||
_LOGGER.warning("No callback available for this datatype")
|
||||
|
|
|
@ -20,7 +20,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
vol.Optional(CONF_ADS_FACTOR): cv.positive_int,
|
||||
vol.Optional(CONF_ADS_TYPE, default=ads.ADSTYPE_INT):
|
||||
vol.In([ads.ADSTYPE_INT, ads.ADSTYPE_UINT, ads.ADSTYPE_BYTE]),
|
||||
vol.In([ads.ADSTYPE_INT, ads.ADSTYPE_UINT, ads.ADSTYPE_BYTE,
|
||||
ads.ADSTYPE_DINT, ads.ADSTYPE_UDINT]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=''): cv.string,
|
||||
})
|
||||
|
|
|
@ -17,7 +17,7 @@ from homeassistant.const import (CONF_LATITUDE, CONF_LONGITUDE,
|
|||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
|
||||
REQUIREMENTS = ['pyMetno==0.4.5']
|
||||
REQUIREMENTS = ['pyMetno==0.4.6']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -91,7 +91,9 @@ class AirSensor(AirQualityEntity):
|
|||
@property
|
||||
def device_state_attributes(self) -> dict:
|
||||
"""Return other details about the sensor state."""
|
||||
return {'level': self._api.data.get('level')}
|
||||
return {'level': self._api.data.get('level'),
|
||||
'location': self._api.data.get('location'),
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
"""
|
||||
Support for openSenseMap Air Quality data.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/air_quality.opensensemap/
|
||||
"""
|
||||
"""Support for openSenseMap Air Quality data."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
|
@ -16,7 +11,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['opensensemap-api==0.1.3']
|
||||
REQUIREMENTS = ['opensensemap-api==0.1.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@ import aiohttp
|
|||
import async_timeout
|
||||
|
||||
from homeassistant.components import (
|
||||
alert, automation, binary_sensor, climate, cover, fan, group, http,
|
||||
alert, automation, binary_sensor, cover, fan, group, http,
|
||||
input_boolean, light, lock, media_player, scene, script, sensor, switch)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.const import (
|
||||
|
@ -22,7 +23,7 @@ from homeassistant.const import (
|
|||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP, SERVICE_VOLUME_SET,
|
||||
SERVICE_VOLUME_MUTE, STATE_LOCKED, STATE_ON, STATE_UNAVAILABLE,
|
||||
SERVICE_VOLUME_MUTE, STATE_LOCKED, STATE_ON, STATE_OFF, STATE_UNAVAILABLE,
|
||||
STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL)
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.color as color_util
|
||||
|
@ -58,7 +59,7 @@ API_THERMOSTAT_MODES = OrderedDict([
|
|||
(climate.STATE_AUTO, 'AUTO'),
|
||||
(climate.STATE_ECO, 'ECO'),
|
||||
(climate.STATE_MANUAL, 'AUTO'),
|
||||
(climate.STATE_OFF, 'OFF'),
|
||||
(STATE_OFF, 'OFF'),
|
||||
(climate.STATE_IDLE, 'OFF'),
|
||||
(climate.STATE_FAN_ONLY, 'OFF'),
|
||||
(climate.STATE_DRY, 'OFF'),
|
||||
|
@ -765,7 +766,7 @@ class _AlexaThermostatController(_AlexaInterface):
|
|||
|
||||
unit = self.hass.config.units.temperature_unit
|
||||
if name == 'targetSetpoint':
|
||||
temp = self.entity.attributes.get(climate.ATTR_TEMPERATURE)
|
||||
temp = self.entity.attributes.get(ATTR_TEMPERATURE)
|
||||
elif name == 'lowerSetpoint':
|
||||
temp = self.entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)
|
||||
elif name == 'upperSetpoint':
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Anwendungsschl\u00fcssel und / oder API-Schl\u00fcssel bereits registriert",
|
||||
"invalid_key": "Ung\u00fcltiger API Key und / oder Anwendungsschl\u00fcssel",
|
||||
"no_devices": "Keine Ger\u00e4te im Konto gefunden"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"app_key": "Anwendungsschl\u00fcssel"
|
||||
},
|
||||
"title": "Gib deine Informationen ein"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Clave de aplicaci\u00f3n y/o clave de API ya registrada",
|
||||
"invalid_key": "Clave de API y/o clave de aplicaci\u00f3n no v\u00e1lida",
|
||||
"no_devices": "No se han encontrado dispositivos en la cuenta."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Clave API",
|
||||
"app_key": "Clave de aplicaci\u00f3n"
|
||||
},
|
||||
"title": "Completa tu informaci\u00f3n"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Completa tu informaci\u00f3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"no_devices": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05d4\u05ea\u05e7\u05df \u05d1\u05d7\u05e9\u05d1\u05d5\u05df"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
|
||||
},
|
||||
"title": "\u05de\u05dc\u05d0 \u05d0\u05ea \u05d4\u05e4\u05e8\u05d8\u05d9\u05dd \u05e9\u05dc\u05da"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Alkalmaz\u00e1s kulcsot \u00e9s/vagy az API kulcsot m\u00e1r regisztr\u00e1lt\u00e1k",
|
||||
"invalid_key": "\u00c9rv\u00e9nytelen API kulcs \u00e9s / vagy alkalmaz\u00e1skulcs",
|
||||
"no_devices": "Nincs a fi\u00f3kodban tal\u00e1lhat\u00f3 eszk\u00f6z"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API kulcs",
|
||||
"app_key": "Alkalmaz\u00e1skulcs"
|
||||
},
|
||||
"title": "T\u00f6ltsd ki az adataid"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "API Key e/o Application Key gi\u00e0 registrata",
|
||||
"invalid_key": "API Key e/o Application Key non valida",
|
||||
"no_devices": "Nessun dispositivo trovato nell'account"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"app_key": "Application Key"
|
||||
},
|
||||
"title": "Inserisci i tuoi dati"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Chave de aplica\u00e7\u00e3o e/ou chave de API j\u00e1 registradas.",
|
||||
"invalid_key": "Chave de API e/ou chave de aplica\u00e7\u00e3o inv\u00e1lidas",
|
||||
"no_devices": "Nenhum dispositivo encontrado na conta"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Chave de API"
|
||||
}
|
||||
"api_key": "Chave de API",
|
||||
"app_key": "Chave de aplica\u00e7\u00e3o"
|
||||
},
|
||||
"title": "Preencha as suas informa\u00e7\u00f5es"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Aplikacijski klju\u010d in / ali klju\u010d API je \u017ee registriran",
|
||||
"invalid_key": "Neveljaven klju\u010d API in / ali klju\u010d aplikacije",
|
||||
"no_devices": "V ra\u010dunu ni najdene nobene naprave"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Klju\u010d",
|
||||
"app_key": "Klju\u010d aplikacije"
|
||||
},
|
||||
"title": "Izpolnite svoje podatke"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Applikationsnyckel och/eller API-nyckel \u00e4r redan registrerade",
|
||||
"invalid_key": "Ogiltigt API-nyckel och/eller applikationsnyckel",
|
||||
"no_devices": "Inga enheter hittades i kontot"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel",
|
||||
"app_key": "Applikationsnyckel"
|
||||
},
|
||||
"title": "Fyll i dina uppgifter"
|
||||
}
|
||||
},
|
||||
"title": "Ambient Weather PWS (Personal Weather Station)"
|
||||
}
|
||||
}
|
|
@ -20,13 +20,14 @@ from .const import (
|
|||
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE,
|
||||
TYPE_BINARY_SENSOR, TYPE_SENSOR)
|
||||
|
||||
REQUIREMENTS = ['aioambient==0.1.2']
|
||||
REQUIREMENTS = ['aioambient==0.1.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_CONFIG = 'config'
|
||||
|
||||
DEFAULT_SOCKET_MIN_RETRY = 15
|
||||
DEFAULT_WATCHDOG_SECONDS = 5 * 60
|
||||
|
||||
TYPE_24HOURRAININ = '24hourrainin'
|
||||
TYPE_BAROMABSIN = 'baromabsin'
|
||||
|
@ -296,6 +297,7 @@ class AmbientStation:
|
|||
"""Initialize."""
|
||||
self._config_entry = config_entry
|
||||
self._hass = hass
|
||||
self._watchdog_listener = None
|
||||
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
|
||||
self.client = client
|
||||
self.monitored_conditions = monitored_conditions
|
||||
|
@ -305,9 +307,18 @@ class AmbientStation:
|
|||
"""Register handlers and connect to the websocket."""
|
||||
from aioambient.errors import WebsocketError
|
||||
|
||||
async def _ws_reconnect(event_time):
|
||||
"""Forcibly disconnect from and reconnect to the websocket."""
|
||||
_LOGGER.debug('Watchdog expired; forcing socket reconnection')
|
||||
await self.client.websocket.disconnect()
|
||||
await self.client.websocket.connect()
|
||||
|
||||
def on_connect():
|
||||
"""Define a handler to fire when the websocket is connected."""
|
||||
_LOGGER.info('Connected to websocket')
|
||||
_LOGGER.debug('Watchdog starting')
|
||||
self._watchdog_listener = async_call_later(
|
||||
self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
|
||||
|
||||
def on_data(data):
|
||||
"""Define a handler to fire when the data is received."""
|
||||
|
@ -317,6 +328,11 @@ class AmbientStation:
|
|||
self.stations[mac_address][ATTR_LAST_DATA] = data
|
||||
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||
|
||||
_LOGGER.debug('Resetting watchdog')
|
||||
self._watchdog_listener()
|
||||
self._watchdog_listener = async_call_later(
|
||||
self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
|
||||
|
||||
def on_disconnect():
|
||||
"""Define a handler to fire when the websocket is disconnected."""
|
||||
_LOGGER.info('Disconnected from websocket')
|
||||
|
|
|
@ -15,7 +15,7 @@ REQUIREMENTS = ['pyarlo==0.2.3']
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by arlo.netgear.com"
|
||||
ATTRIBUTION = "Data provided by arlo.netgear.com"
|
||||
|
||||
DATA_ARLO = 'data_arlo'
|
||||
DEFAULT_BRAND = 'Netgear Arlo'
|
||||
|
|
|
@ -9,7 +9,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanel, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.arlo import (
|
||||
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
|
||||
DATA_ARLO, ATTRIBUTION, SIGNAL_UPDATE_ARLO)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT)
|
||||
|
@ -117,7 +117,7 @@ class ArloBaseStation(AlarmControlPanel):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
'device_id': self._base_station.device_id
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import voluptuous as vol
|
|||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.arlo import (
|
||||
CONF_ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
|
||||
ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, TEMP_CELSIUS,
|
||||
|
@ -177,7 +177,7 @@ class ArloSensor(Entity):
|
|||
"""Return the device state attributes."""
|
||||
attrs = {}
|
||||
|
||||
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
|
||||
attrs['brand'] = DEFAULT_BRAND
|
||||
|
||||
if self._sensor_type != 'total_cameras':
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "No hay servicios de notificaci\u00f3n disponibles."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor int\u00e9ntelo de nuevo."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Por favor seleccione uno de los servicios de notificaci\u00f3n:",
|
||||
"title": "Configure la contrase\u00f1a de un solo uso entregada por el componente de notificaci\u00f3n"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Se ha enviado una contrase\u00f1a \u00fanica a trav\u00e9s de **notify.{notify_service}**. Por favor ingr\u00e9selo a continuaci\u00f3n:",
|
||||
"title": "Verificar la configuracion"
|
||||
}
|
||||
}
|
||||
},
|
||||
"totp": {
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Para activar la autenticaci\u00f3n de dos factores utilizando contrase\u00f1as de un solo uso basadas en el tiempo, escanee el c\u00f3digo QR con su aplicaci\u00f3n de autenticaci\u00f3n. Si no tiene uno, le recomendamos [Autenticador de Google] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Despu\u00e9s de escanear el c\u00f3digo, ingrese el c\u00f3digo de seis d\u00edgitos de su aplicaci\u00f3n para verificar la configuraci\u00f3n. Si tiene problemas para escanear el c\u00f3digo QR, realice una configuraci\u00f3n manual con el c\u00f3digo ** ` {code} ` **.",
|
||||
"title": "Configurar la autenticaci\u00f3n de dos factores mediante TOTP"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:"
|
||||
"description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:",
|
||||
"title": "\u00c1ll\u00edtsa be az \u00e9rtes\u00edt\u00e9si \u00f6sszetev\u0151 \u00e1ltal megadott egyszeri jelsz\u00f3t"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Be\u00e1ll\u00edt\u00e1s ellen\u0151rz\u00e9se"
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Selezionare uno dei servizi di notifica:"
|
||||
"description": "Selezionare uno dei servizi di notifica:",
|
||||
"title": "Imposta la password one-time fornita dal componente di notifica"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u00c8 stata inviata una password monouso tramite **notify.{notify_service}**. Per favore, inseriscila qui sotto:",
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u0415\u0441\u043b\u0438 \u0432\u044b \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0447\u0430\u0441\u044b \u0432 \u0432\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 Home Assistant \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f."
|
||||
"invalid_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u0415\u0441\u043b\u0438 \u0412\u044b \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0447\u0430\u0441\u044b \u0432 \u0412\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 Home Assistant \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy](https://authy.com/). \n\n {qr_code} \n \n\u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0434\u0430 **`{code}`**.",
|
||||
"description": "\u0427\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0442\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442, \u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u043b\u0438\u0431\u043e [Authy](https://authy.com/). \n\n {qr_code} \n \n\u041f\u043e\u0441\u043b\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f QR-\u043a\u043e\u0434\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0412\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u0415\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c QR-\u043a\u043e\u0434\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0434\u0430 **`{code}`**.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c TOTP"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -19,8 +19,8 @@ from homeassistant.util import Throttle
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric" \
|
||||
"Administration"
|
||||
ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric " \
|
||||
"Administration"
|
||||
CONF_THRESHOLD = 'forecast_threshold'
|
||||
|
||||
DEFAULT_DEVICE_CLASS = 'visible'
|
||||
|
@ -91,7 +91,7 @@ class AuroraSensor(BinarySensorDevice):
|
|||
if self.aurora_data:
|
||||
attrs['visibility_level'] = self.aurora_data.visibility_level
|
||||
attrs['message'] = self.aurora_data.is_visible_text
|
||||
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
|
||||
return attrs
|
||||
|
||||
def update(self):
|
||||
|
|
|
@ -18,7 +18,7 @@ from homeassistant.const import (
|
|||
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
|
||||
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
|
||||
|
||||
REQUIREMENTS = ['pyhik==0.1.9']
|
||||
REQUIREMENTS = ['pyhik==0.2.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_IGNORED = 'ignored'
|
||||
|
@ -51,6 +51,8 @@ DEVICE_CLASS_MAP = {
|
|||
'Unattended Baggage': 'motion',
|
||||
'Attended Baggage': 'motion',
|
||||
'Recording Failure': None,
|
||||
'Exiting Region': 'motion',
|
||||
'Entering Region': 'motion',
|
||||
}
|
||||
|
||||
CUSTOMIZE_SCHEMA = vol.Schema({
|
||||
|
|
|
@ -14,7 +14,7 @@ from homeassistant.components.binary_sensor import (
|
|||
from homeassistant.components.sensor.rest import RestData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT,
|
||||
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
|
||||
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
DEFAULT_METHOD = 'GET'
|
||||
DEFAULT_NAME = 'REST Binary Sensor'
|
||||
DEFAULT_VERIFY_SSL = True
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_RESOURCE): cv.url,
|
||||
|
@ -39,6 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
})
|
||||
|
||||
|
||||
|
@ -49,6 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
method = config.get(CONF_METHOD)
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
verify_ssl = config.get(CONF_VERIFY_SSL)
|
||||
timeout = config.get(CONF_TIMEOUT)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
headers = config.get(CONF_HEADERS)
|
||||
|
@ -65,7 +68,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
else:
|
||||
auth = None
|
||||
|
||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
|
||||
rest = RestData(method, resource, auth, headers, payload, verify_ssl,
|
||||
timeout)
|
||||
rest.update()
|
||||
if rest.data is None:
|
||||
raise PlatformNotReady
|
||||
|
|
|
@ -11,7 +11,7 @@ import voluptuous as vol
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from homeassistant.components.ring import (
|
||||
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING)
|
||||
ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING)
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
|
||||
|
@ -47,18 +47,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
for device in ring.doorbells: # ring.doorbells is doing I/O
|
||||
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
|
||||
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
|
||||
sensors.append(RingBinarySensor(hass,
|
||||
device,
|
||||
sensor_type))
|
||||
sensors.append(RingBinarySensor(hass, device, sensor_type))
|
||||
|
||||
for device in ring.stickup_cams: # ring.stickup_cams is doing I/O
|
||||
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
|
||||
if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]:
|
||||
sensors.append(RingBinarySensor(hass,
|
||||
device,
|
||||
sensor_type))
|
||||
sensors.append(RingBinarySensor(hass, device, sensor_type))
|
||||
|
||||
add_entities(sensors, True)
|
||||
return True
|
||||
|
||||
|
||||
class RingBinarySensor(BinarySensorDevice):
|
||||
|
@ -69,8 +65,8 @@ class RingBinarySensor(BinarySensorDevice):
|
|||
super(RingBinarySensor, self).__init__()
|
||||
self._sensor_type = sensor_type
|
||||
self._data = data
|
||||
self._name = "{0} {1}".format(self._data.name,
|
||||
SENSOR_TYPES.get(self._sensor_type)[0])
|
||||
self._name = "{0} {1}".format(
|
||||
self._data.name, SENSOR_TYPES.get(self._sensor_type)[0])
|
||||
self._device_class = SENSOR_TYPES.get(self._sensor_type)[2]
|
||||
self._state = None
|
||||
self._unique_id = '{}-{}'.format(self._data.id, self._sensor_type)
|
||||
|
@ -99,7 +95,7 @@ class RingBinarySensor(BinarySensorDevice):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attrs = {}
|
||||
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
|
||||
|
||||
attrs['device_id'] = self._data.id
|
||||
attrs['firmware'] = self._data.firmware
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
"""Support for representing current time of the day as binary sensors."""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
import pytz
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_AFTER, CONF_BEFORE, CONF_NAME, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.sun import (
|
||||
get_astral_event_date, get_astral_event_next)
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_AFTER = 'after'
|
||||
ATTR_BEFORE = 'before'
|
||||
ATTR_NEXT_UPDATE = 'next_update'
|
||||
|
||||
CONF_AFTER_OFFSET = 'after_offset'
|
||||
CONF_BEFORE_OFFSET = 'before_offset'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_AFTER):
|
||||
vol.Any(cv.time, vol.All(vol.Lower, cv.sun_event)),
|
||||
vol.Required(CONF_BEFORE):
|
||||
vol.Any(cv.time, vol.All(vol.Lower, cv.sun_event)),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_AFTER_OFFSET, default=timedelta(0)): cv.time_period,
|
||||
vol.Optional(CONF_BEFORE_OFFSET, default=timedelta(0)): cv.time_period,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the ToD sensors."""
|
||||
if hass.config.time_zone is None:
|
||||
_LOGGER.error("Timezone is not set in Home Assistant configuration")
|
||||
return
|
||||
|
||||
after = config[CONF_AFTER]
|
||||
after_offset = config[CONF_AFTER_OFFSET]
|
||||
before = config[CONF_BEFORE]
|
||||
before_offset = config[CONF_BEFORE_OFFSET]
|
||||
name = config[CONF_NAME]
|
||||
sensor = TodSensor(name, after, after_offset, before, before_offset)
|
||||
|
||||
async_add_entities([sensor])
|
||||
|
||||
|
||||
def is_sun_event(event):
|
||||
"""Return true if event is sun event not time."""
|
||||
return event in (SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET)
|
||||
|
||||
|
||||
class TodSensor(BinarySensorDevice):
|
||||
"""Time of the Day Sensor."""
|
||||
|
||||
def __init__(self, name, after, after_offset, before, before_offset):
|
||||
"""Init the ToD Sensor..."""
|
||||
self._name = name
|
||||
self._time_before = self._time_after = self._next_update = None
|
||||
self._after_offset = after_offset
|
||||
self._before_offset = before_offset
|
||||
self._before = before
|
||||
self._after = after
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Sensor does not need to be polled."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def after(self):
|
||||
"""Return the timestamp for the begining of the period."""
|
||||
return self._time_after
|
||||
|
||||
@property
|
||||
def before(self):
|
||||
"""Return the timestamp for the end of the period."""
|
||||
return self._time_before
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True is sensor is on."""
|
||||
if self.after < self.before:
|
||||
return self.after <= self.current_datetime < self.before
|
||||
return False
|
||||
|
||||
@property
|
||||
def current_datetime(self):
|
||||
"""Return local current datetime according to hass configuration."""
|
||||
return dt_util.utcnow()
|
||||
|
||||
@property
|
||||
def next_update(self):
|
||||
"""Return the next update point in the UTC time."""
|
||||
return self._next_update
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
return {
|
||||
ATTR_AFTER: self.after.astimezone(
|
||||
self.hass.config.time_zone).isoformat(),
|
||||
ATTR_BEFORE: self.before.astimezone(
|
||||
self.hass.config.time_zone).isoformat(),
|
||||
ATTR_NEXT_UPDATE: self.next_update.astimezone(
|
||||
self.hass.config.time_zone).isoformat(),
|
||||
}
|
||||
|
||||
def _calculate_initial_boudary_time(self):
|
||||
"""Calculate internal absolute time boudaries."""
|
||||
nowutc = self.current_datetime
|
||||
# If after value is a sun event instead of absolute time
|
||||
if is_sun_event(self._after):
|
||||
# Calculate the today's event utc time or
|
||||
# if not available take next
|
||||
after_event_date = \
|
||||
get_astral_event_date(self.hass, self._after, nowutc) or \
|
||||
get_astral_event_next(self.hass, self._after, nowutc)
|
||||
else:
|
||||
# Convert local time provided to UTC today
|
||||
# datetime.combine(date, time, tzinfo) is not supported
|
||||
# in python 3.5. The self._after is provided
|
||||
# with hass configured TZ not system wide
|
||||
after_event_date = datetime.combine(
|
||||
nowutc, self._after.replace(
|
||||
tzinfo=self.hass.config.time_zone)).astimezone(tz=pytz.UTC)
|
||||
|
||||
self._time_after = after_event_date
|
||||
|
||||
# If before value is a sun event instead of absolute time
|
||||
if is_sun_event(self._before):
|
||||
# Calculate the today's event utc time or if not available take
|
||||
# next
|
||||
before_event_date = \
|
||||
get_astral_event_date(self.hass, self._before, nowutc) or \
|
||||
get_astral_event_next(self.hass, self._before, nowutc)
|
||||
# Before is earlier than after
|
||||
if before_event_date < after_event_date:
|
||||
# Take next day for before
|
||||
before_event_date = get_astral_event_next(
|
||||
self.hass, self._before, after_event_date)
|
||||
else:
|
||||
# Convert local time provided to UTC today, see above
|
||||
before_event_date = datetime.combine(
|
||||
nowutc, self._before.replace(
|
||||
tzinfo=self.hass.config.time_zone)).astimezone(tz=pytz.UTC)
|
||||
|
||||
# It is safe to add timedelta days=1 to UTC as there is no DST
|
||||
if before_event_date < after_event_date + self._after_offset:
|
||||
before_event_date += timedelta(days=1)
|
||||
|
||||
self._time_before = before_event_date
|
||||
|
||||
# Add offset to utc boundaries according to the configuration
|
||||
self._time_after += self._after_offset
|
||||
self._time_before += self._before_offset
|
||||
|
||||
def _turn_to_next_day(self):
|
||||
"""Turn to to the next day."""
|
||||
if is_sun_event(self._after):
|
||||
self._time_after = get_astral_event_next(
|
||||
self.hass, self._after,
|
||||
self._time_after - self._after_offset)
|
||||
self._time_after += self._after_offset
|
||||
else:
|
||||
# Offset is already there
|
||||
self._time_after += timedelta(days=1)
|
||||
|
||||
if is_sun_event(self._before):
|
||||
self._time_before = get_astral_event_next(
|
||||
self.hass, self._before,
|
||||
self._time_before - self._before_offset)
|
||||
self._time_before += self._before_offset
|
||||
else:
|
||||
# Offset is already there
|
||||
self._time_before += timedelta(days=1)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity about to be added to Home Assistant."""
|
||||
await super().async_added_to_hass()
|
||||
self._calculate_initial_boudary_time()
|
||||
self._calculate_next_update()
|
||||
self._point_in_time_listener(dt_util.now())
|
||||
|
||||
def _calculate_next_update(self):
|
||||
"""Datetime when the next update to the state."""
|
||||
now = self.current_datetime
|
||||
if now < self.after:
|
||||
self._next_update = self.after
|
||||
return
|
||||
if now < self.before:
|
||||
self._next_update = self.before
|
||||
return
|
||||
self._turn_to_next_day()
|
||||
self._next_update = self.after
|
||||
|
||||
@callback
|
||||
def _point_in_time_listener(self, now):
|
||||
"""Run when the state of the sensor should be updated."""
|
||||
self._calculate_next_update()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async_track_point_in_utc_time(
|
||||
self.hass, self._point_in_time_listener, self.next_update)
|
|
@ -1,9 +1,4 @@
|
|||
"""
|
||||
A sensor that monitors trends in other components.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.trend/
|
||||
"""
|
||||
"""A sensor that monitors trends in other components."""
|
||||
from collections import deque
|
||||
import logging
|
||||
import math
|
||||
|
@ -22,7 +17,7 @@ from homeassistant.helpers.entity import generate_entity_id
|
|||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.util import utcnow
|
||||
|
||||
REQUIREMENTS = ['numpy==1.16.0']
|
||||
REQUIREMENTS = ['numpy==1.16.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
ATTR_TARGET = 'target'
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by Uptime Robot"
|
||||
ATTRIBUTION = "Data provided by Uptime Robot"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
|
@ -78,7 +78,7 @@ class UptimeRobotBinarySensor(BinarySensorDevice):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the binary sensor."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
ATTR_TARGET: self._target,
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class BloomSky:
|
|||
self.API_URL, headers={AUTHORIZATION: self._api_key}, timeout=10)
|
||||
if response.status_code == 401:
|
||||
raise RuntimeError("Invalid API_KEY")
|
||||
elif response.status_code != 200:
|
||||
if response.status_code != 200:
|
||||
_LOGGER.error("Invalid HTTP response: %s", response.status_code)
|
||||
return
|
||||
# Create dictionary keyed off of the device unique id
|
||||
|
|
|
@ -13,7 +13,7 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.components.ring import (
|
||||
DATA_RING, CONF_ATTRIBUTION, NOTIFICATION_ID)
|
||||
DATA_RING, ATTRIBUTION, NOTIFICATION_ID)
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL
|
||||
|
@ -34,8 +34,7 @@ SCAN_INTERVAL = timedelta(seconds=90)
|
|||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||
cv.time_period,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period,
|
||||
})
|
||||
|
||||
|
||||
|
@ -106,7 +105,7 @@ class RingCam(Camera):
|
|||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
'device_id': self._camera.id,
|
||||
'firmware': self._camera.firmware,
|
||||
'kind': self._camera.kind,
|
||||
|
|
|
@ -51,6 +51,17 @@ from .const import (
|
|||
SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW,
|
||||
SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_HOLD_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
SUPPORT_AWAY_MODE,
|
||||
SUPPORT_AUX_HEAT,
|
||||
)
|
||||
from .reproduce_state import async_reproduce_states # noqa
|
||||
|
||||
|
@ -62,29 +73,6 @@ DEFAULT_MAX_HUMIDITY = 99
|
|||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
SCAN_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
STATE_HEAT = 'heat'
|
||||
STATE_COOL = 'cool'
|
||||
STATE_IDLE = 'idle'
|
||||
STATE_AUTO = 'auto'
|
||||
STATE_MANUAL = 'manual'
|
||||
STATE_DRY = 'dry'
|
||||
STATE_FAN_ONLY = 'fan_only'
|
||||
STATE_ECO = 'eco'
|
||||
|
||||
SUPPORT_TARGET_TEMPERATURE = 1
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW = 4
|
||||
SUPPORT_TARGET_HUMIDITY = 8
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH = 16
|
||||
SUPPORT_TARGET_HUMIDITY_LOW = 32
|
||||
SUPPORT_FAN_MODE = 64
|
||||
SUPPORT_OPERATION_MODE = 128
|
||||
SUPPORT_HOLD_MODE = 256
|
||||
SUPPORT_SWING_MODE = 512
|
||||
SUPPORT_AWAY_MODE = 1024
|
||||
SUPPORT_AUX_HEAT = 2048
|
||||
SUPPORT_ON_OFF = 4096
|
||||
|
||||
CONVERTIBLE_ATTRIBUTE = [
|
||||
ATTR_TEMPERATURE,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
|
|
|
@ -20,6 +20,11 @@ ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
|
|||
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
|
||||
ATTR_TARGET_TEMP_STEP = 'target_temp_step'
|
||||
|
||||
DEFAULT_MIN_TEMP = 7
|
||||
DEFAULT_MAX_TEMP = 35
|
||||
DEFAULT_MIN_HUMITIDY = 30
|
||||
DEFAULT_MAX_HUMIDITY = 99
|
||||
|
||||
DOMAIN = 'climate'
|
||||
|
||||
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
|
||||
|
@ -30,3 +35,26 @@ SERVICE_SET_HUMIDITY = 'set_humidity'
|
|||
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
|
||||
SERVICE_SET_SWING_MODE = 'set_swing_mode'
|
||||
SERVICE_SET_TEMPERATURE = 'set_temperature'
|
||||
|
||||
STATE_HEAT = 'heat'
|
||||
STATE_COOL = 'cool'
|
||||
STATE_IDLE = 'idle'
|
||||
STATE_AUTO = 'auto'
|
||||
STATE_MANUAL = 'manual'
|
||||
STATE_DRY = 'dry'
|
||||
STATE_FAN_ONLY = 'fan_only'
|
||||
STATE_ECO = 'eco'
|
||||
|
||||
SUPPORT_TARGET_TEMPERATURE = 1
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW = 4
|
||||
SUPPORT_TARGET_HUMIDITY = 8
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH = 16
|
||||
SUPPORT_TARGET_HUMIDITY_LOW = 32
|
||||
SUPPORT_FAN_MODE = 64
|
||||
SUPPORT_OPERATION_MODE = 128
|
||||
SUPPORT_HOLD_MODE = 256
|
||||
SUPPORT_SWING_MODE = 512
|
||||
SUPPORT_AWAY_MODE = 1024
|
||||
SUPPORT_AUX_HEAT = 2048
|
||||
SUPPORT_ON_OFF = 4096
|
||||
|
|
|
@ -9,10 +9,11 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
|
||||
STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
|
|
@ -4,8 +4,9 @@ Demo platform that offers a fake climate device.
|
|||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE,
|
||||
|
|
|
@ -7,8 +7,9 @@ https://home-assistant.io/components/climate.dyson/
|
|||
import logging
|
||||
|
||||
from homeassistant.components.dyson import DYSON_DEVICES
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE,
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_COOL, STATE_IDLE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ import logging
|
|||
from datetime import timedelta
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_OFF,
|
||||
STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE)
|
||||
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, STATE_OFF)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyephember==0.2.0']
|
||||
|
|
|
@ -8,13 +8,14 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
STATE_ON, STATE_OFF, STATE_HEAT, STATE_MANUAL, STATE_ECO, PLATFORM_SCHEMA,
|
||||
ClimateDevice,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_MANUAL, STATE_ECO,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
|
||||
SUPPORT_ON_OFF)
|
||||
from homeassistant.const import (
|
||||
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
|
||||
ATTR_TEMPERATURE, CONF_MAC, CONF_DEVICES, STATE_ON, STATE_OFF,
|
||||
TEMP_CELSIUS, PRECISION_HALVES)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.45']
|
||||
|
|
|
@ -17,8 +17,9 @@ import voluptuous as vol
|
|||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_SLAVE, TEMP_CELSIUS,
|
||||
ATTR_TEMPERATURE, DEVICE_DEFAULT_NAME)
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_FAN_MODE)
|
||||
from homeassistant.components.modbus import (
|
||||
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
|
||||
|
|
|
@ -11,10 +11,11 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||
from homeassistant.components.climate import (
|
||||
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO,
|
||||
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
|
||||
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN, PRECISION_HALVES,
|
||||
|
|
|
@ -8,8 +8,9 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
|
|
@ -12,8 +12,9 @@ import requests
|
|||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, ATTR_FAN_MODE, ATTR_FAN_LIST,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_FAN_MODE, ATTR_FAN_LIST,
|
||||
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import (
|
||||
|
|
|
@ -6,8 +6,9 @@ https://home-assistant.io/components/climate.melissa/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_ON_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, SUPPORT_FAN_MODE
|
||||
)
|
||||
|
|
|
@ -9,8 +9,9 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, DOMAIN, PLATFORM_SCHEMA, STATE_HEAT,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, STATE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import (
|
||||
|
|
|
@ -9,8 +9,8 @@ from datetime import timedelta
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice,
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN,
|
||||
SUPPORT_HOLD_MODE,
|
||||
SUPPORT_OPERATION_MODE,
|
||||
|
|
|
@ -13,11 +13,12 @@ import requests
|
|||
import voluptuous as vol
|
||||
|
||||
# Import the device class from the component that you want to support
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, ATTR_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE)
|
||||
from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['oemthermostat==1.1']
|
||||
|
|
|
@ -6,11 +6,12 @@ https://home-assistant.io/components/climate.proliphix/
|
|||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
PRECISION_TENTHS, STATE_COOL, STATE_HEAT, STATE_IDLE,
|
||||
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, PRECISION_TENTHS, TEMP_FAHRENHEIT,
|
||||
ATTR_TEMPERATURE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['proliphix==0.4.1']
|
||||
|
|
|
@ -9,12 +9,14 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_ON, STATE_OFF,
|
||||
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, PRECISION_HALVES)
|
||||
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON,
|
||||
STATE_OFF)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['radiotherm==2.0.0']
|
||||
|
|
|
@ -15,8 +15,9 @@ import voluptuous as vol
|
|||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID,
|
||||
STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_CURRENT_HUMIDITY, DOMAIN,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
|
||||
|
|
|
@ -8,8 +8,9 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
|
||||
SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
|
||||
ClimateDevice)
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
|
||||
CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS,
|
||||
|
|
|
@ -8,10 +8,11 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_OPERATION_MODE, PLATFORM_SCHEMA, STATE_COOL, STATE_DRY,
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
|
|
@ -17,6 +17,10 @@ async def async_setup(hass):
|
|||
hass.http.register_view(
|
||||
ConfigManagerFlowResourceView(hass.config_entries.flow))
|
||||
hass.http.register_view(ConfigManagerAvailableFlowView)
|
||||
hass.http.register_view(
|
||||
OptionManagerFlowIndexView(hass.config_entries.options.flow))
|
||||
hass.http.register_view(
|
||||
OptionManagerFlowResourceView(hass.config_entries.options.flow))
|
||||
return True
|
||||
|
||||
|
||||
|
@ -45,8 +49,9 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
|
|||
name = 'api:config:config_entries:entry'
|
||||
|
||||
async def get(self, request):
|
||||
"""List flows in progress."""
|
||||
"""List available config entries."""
|
||||
hass = request.app['hass']
|
||||
|
||||
return self.json([{
|
||||
'entry_id': entry.entry_id,
|
||||
'domain': entry.domain,
|
||||
|
@ -54,6 +59,9 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
|
|||
'source': entry.source,
|
||||
'state': entry.state,
|
||||
'connection_class': entry.connection_class,
|
||||
'supports_options': hasattr(
|
||||
config_entries.HANDLERS[entry.domain],
|
||||
'async_get_options_flow'),
|
||||
} for entry in hass.config_entries.async_entries()])
|
||||
|
||||
|
||||
|
@ -145,3 +153,48 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
|
|||
async def get(self, request):
|
||||
"""List available flow handlers."""
|
||||
return self.json(config_entries.FLOWS)
|
||||
|
||||
|
||||
class OptionManagerFlowIndexView(FlowManagerIndexView):
|
||||
"""View to create option flows."""
|
||||
|
||||
url = '/api/config/config_entries/entry/option/flow'
|
||||
name = 'api:config:config_entries:entry:resource:option:flow'
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def post(self, request):
|
||||
"""Handle a POST request.
|
||||
|
||||
handler in request is entry_id.
|
||||
"""
|
||||
if not request['hass_user'].is_admin:
|
||||
raise Unauthorized(
|
||||
perm_category=CAT_CONFIG_ENTRIES, permission='edit')
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
return await super().post(request)
|
||||
|
||||
|
||||
class OptionManagerFlowResourceView(ConfigManagerFlowResourceView):
|
||||
"""View to interact with the option flow manager."""
|
||||
|
||||
url = '/api/config/config_entries/options/flow/{flow_id}'
|
||||
name = 'api:config:config_entries:options:flow:resource'
|
||||
|
||||
async def get(self, request, flow_id):
|
||||
"""Get the current state of a data_entry_flow."""
|
||||
if not request['hass_user'].is_admin:
|
||||
raise Unauthorized(
|
||||
perm_category=CAT_CONFIG_ENTRIES, permission='edit')
|
||||
|
||||
return await super().get(request, flow_id)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def post(self, request, flow_id):
|
||||
"""Handle a POST request."""
|
||||
if not request['hass_user'].is_admin:
|
||||
raise Unauthorized(
|
||||
perm_category=CAT_CONFIG_ENTRIES, permission='edit')
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
return await super().post(request, flow_id)
|
||||
|
|
|
@ -19,6 +19,7 @@ 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),
|
||||
vol.Optional('name_by_user'): vol.Any(str, None),
|
||||
})
|
||||
|
||||
|
||||
|
@ -49,11 +50,13 @@ 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'])
|
||||
msg.pop('type')
|
||||
msg_id = msg.pop('id')
|
||||
|
||||
entry = registry.async_update_device(**msg)
|
||||
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], _entry_dict(entry)
|
||||
msg_id, _entry_dict(entry)
|
||||
))
|
||||
|
||||
|
||||
|
@ -70,4 +73,5 @@ def _entry_dict(entry):
|
|||
'id': entry.id,
|
||||
'hub_device_id': entry.hub_device_id,
|
||||
'area_id': entry.area_id,
|
||||
'name_by_user': entry.name_by_user,
|
||||
}
|
||||
|
|
|
@ -35,12 +35,27 @@ ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
|
|||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
# Refer to the cover dev docs for device class descriptions
|
||||
DEVICE_CLASS_AWNING = 'awning'
|
||||
DEVICE_CLASS_BLIND = 'blind'
|
||||
DEVICE_CLASS_CURTAIN = 'curtain'
|
||||
DEVICE_CLASS_DAMPER = 'damper'
|
||||
DEVICE_CLASS_DOOR = 'door'
|
||||
DEVICE_CLASS_GARAGE = 'garage'
|
||||
DEVICE_CLASS_SHADE = 'shade'
|
||||
DEVICE_CLASS_SHUTTER = 'shutter'
|
||||
DEVICE_CLASS_WINDOW = 'window'
|
||||
DEVICE_CLASSES = [
|
||||
'damper',
|
||||
'garage', # Garage door control
|
||||
'window', # Window control
|
||||
DEVICE_CLASS_AWNING,
|
||||
DEVICE_CLASS_BLIND,
|
||||
DEVICE_CLASS_CURTAIN,
|
||||
DEVICE_CLASS_DAMPER,
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_GARAGE,
|
||||
DEVICE_CLASS_SHADE,
|
||||
DEVICE_CLASS_SHUTTER,
|
||||
DEVICE_CLASS_WINDOW
|
||||
]
|
||||
|
||||
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
|
||||
|
||||
SUPPORT_OPEN = 1
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"device_timeout": "Tiempo de espera de conexi\u00f3n al dispositivo."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Introduzca la direcci\u00f3n IP de su Daikin AC.",
|
||||
"title": "Configurar Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
|
@ -1,11 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk",
|
||||
"device_fail": "Az eszk\u00f6z l\u00e9trehoz\u00e1sakor v\u00e1ratlan hiba l\u00e9pett fel.",
|
||||
"device_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00e9sz\u00fcl\u00e9k csatlakoz\u00e1sakor."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Kiszolg\u00e1l\u00f3"
|
||||
}
|
||||
},
|
||||
"description": "Add meg a Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 IP-c\u00edm\u00e9t.",
|
||||
"title": "A Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 konfigur\u00e1l\u00e1sa"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Daikin L\u00e9gkond\u00edci\u00f3n\u00e1l\u00f3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
|
||||
"device_fail": "Errore inatteso durante la creazione del dispositivo.",
|
||||
"device_timeout": "Tempo scaduto per la connessione al dispositivo."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Inserisci l'indirizzo IP del tuo Daikin AC.",
|
||||
"title": "Configura Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
"data": {
|
||||
"host": "\u0425\u043e\u0441\u0442"
|
||||
},
|
||||
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0432\u0430\u0448\u0435\u0433\u043e Daikin AC.",
|
||||
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0412\u0430\u0448\u0435\u0433\u043e Daikin AC.",
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Enheten \u00e4r redan konfigurerad",
|
||||
"device_fail": "Ov\u00e4ntat fel vid skapande av enhet.",
|
||||
"device_timeout": "Timeout f\u00f6r anslutning till enheten."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "V\u00e4rddatorn"
|
||||
},
|
||||
"description": "Ange IP-adressen f\u00f6r din Daikin AC.",
|
||||
"title": "Konfigurera Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
|
@ -4,17 +4,17 @@ import re
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE,
|
||||
ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
ClimateDevice)
|
||||
ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN
|
||||
from homeassistant.components.daikin.const import (
|
||||
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -9,11 +9,11 @@ from homeassistant.helpers import discovery
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['pydanfossair==0.0.6']
|
||||
REQUIREMENTS = ['pydanfossair==0.0.7']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor']
|
||||
DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor', 'switch']
|
||||
DOMAIN = 'danfoss_air'
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
@ -52,6 +52,10 @@ class DanfossAir:
|
|||
"""Get value for sensor."""
|
||||
return self._data.get(item)
|
||||
|
||||
def update_state(self, command, state_command):
|
||||
"""Send update command to Danfoss Air CCM."""
|
||||
self._data[state_command] = self._client.command(command)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Use the data from Danfoss Air API."""
|
||||
|
@ -71,5 +75,17 @@ class DanfossAir:
|
|||
= round(self._client.command(ReadCommand.filterPercent), 2)
|
||||
self._data[ReadCommand.bypass] \
|
||||
= self._client.command(ReadCommand.bypass)
|
||||
self._data[ReadCommand.fan_step] \
|
||||
= self._client.command(ReadCommand.fan_step)
|
||||
self._data[ReadCommand.supply_fan_speed] \
|
||||
= self._client.command(ReadCommand.supply_fan_speed)
|
||||
self._data[ReadCommand.exhaust_fan_speed] \
|
||||
= self._client.command(ReadCommand.exhaust_fan_speed)
|
||||
self._data[ReadCommand.away_mode] \
|
||||
= self._client.command(ReadCommand.away_mode)
|
||||
self._data[ReadCommand.boost] \
|
||||
= self._client.command(ReadCommand.boost)
|
||||
self._data[ReadCommand.battery_percent] \
|
||||
= self._client.command(ReadCommand.battery_percent)
|
||||
|
||||
_LOGGER.debug("Done fetching data from Danfoss Air CCM module")
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
"""
|
||||
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/
|
||||
"""
|
||||
"""Support for the for Danfoss Air HRV binary sensors."""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.danfoss_air import DOMAIN \
|
||||
as DANFOSS_AIR_DOMAIN
|
||||
|
@ -14,12 +9,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
from pydanfossair.commands import ReadCommand
|
||||
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||
|
||||
sensors = [["Danfoss Air Bypass Active", ReadCommand.bypass]]
|
||||
sensors = [
|
||||
["Danfoss Air Bypass Active", ReadCommand.bypass, "opening"],
|
||||
["Danfoss Air Away Mode Active", ReadCommand.away_mode, None],
|
||||
]
|
||||
|
||||
dev = []
|
||||
|
||||
for sensor in sensors:
|
||||
dev.append(DanfossAirBinarySensor(data, sensor[0], sensor[1]))
|
||||
dev.append(DanfossAirBinarySensor(
|
||||
data, sensor[0], sensor[1], sensor[2]))
|
||||
|
||||
add_entities(dev, True)
|
||||
|
||||
|
@ -27,12 +26,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
class DanfossAirBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Danfoss Air binary sensor."""
|
||||
|
||||
def __init__(self, data, name, sensor_type):
|
||||
def __init__(self, data, name, sensor_type, device_class):
|
||||
"""Initialize the Danfoss Air binary sensor."""
|
||||
self._data = data
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._type = sensor_type
|
||||
self._device_class = device_class
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -47,7 +47,7 @@ class DanfossAirBinarySensor(BinarySensorDevice):
|
|||
@property
|
||||
def device_class(self):
|
||||
"""Type of device class."""
|
||||
return "opening"
|
||||
return self._device_class
|
||||
|
||||
def update(self):
|
||||
"""Fetch new state data for the sensor."""
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
"""
|
||||
Support for the for Danfoss Air HRV sensor platform.
|
||||
"""Support for the for Danfoss Air HRV sensors."""
|
||||
import logging
|
||||
|
||||
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.const import (
|
||||
TEMP_CELSIUS, DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the available Danfoss Air sensors etc."""
|
||||
|
@ -18,23 +19,32 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
|
||||
sensors = [
|
||||
["Danfoss Air Exhaust Temperature", TEMP_CELSIUS,
|
||||
ReadCommand.exhaustTemperature],
|
||||
ReadCommand.exhaustTemperature, DEVICE_CLASS_TEMPERATURE],
|
||||
["Danfoss Air Outdoor Temperature", TEMP_CELSIUS,
|
||||
ReadCommand.outdoorTemperature],
|
||||
ReadCommand.outdoorTemperature, DEVICE_CLASS_TEMPERATURE],
|
||||
["Danfoss Air Supply Temperature", TEMP_CELSIUS,
|
||||
ReadCommand.supplyTemperature],
|
||||
ReadCommand.supplyTemperature, DEVICE_CLASS_TEMPERATURE],
|
||||
["Danfoss Air Extract Temperature", TEMP_CELSIUS,
|
||||
ReadCommand.extractTemperature],
|
||||
ReadCommand.extractTemperature, DEVICE_CLASS_TEMPERATURE],
|
||||
["Danfoss Air Remaining Filter", '%',
|
||||
ReadCommand.filterPercent],
|
||||
ReadCommand.filterPercent, None],
|
||||
["Danfoss Air Humidity", '%',
|
||||
ReadCommand.humidity]
|
||||
ReadCommand.humidity, DEVICE_CLASS_HUMIDITY],
|
||||
["Danfoss Air Fan Step", '%',
|
||||
ReadCommand.fan_step, None],
|
||||
["Dandoss Air Exhaust Fan Speed", 'RPM',
|
||||
ReadCommand.exhaust_fan_speed, None],
|
||||
["Dandoss Air Supply Fan Speed", 'RPM',
|
||||
ReadCommand.supply_fan_speed, None],
|
||||
["Dandoss Air Dial Battery", '%',
|
||||
ReadCommand.battery_percent, DEVICE_CLASS_BATTERY]
|
||||
]
|
||||
|
||||
dev = []
|
||||
|
||||
for sensor in sensors:
|
||||
dev.append(DanfossAir(data, sensor[0], sensor[1], sensor[2]))
|
||||
dev.append(DanfossAir(
|
||||
data, sensor[0], sensor[1], sensor[2], sensor[3]))
|
||||
|
||||
add_entities(dev, True)
|
||||
|
||||
|
@ -42,19 +52,25 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
class DanfossAir(Entity):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
def __init__(self, data, name, sensor_unit, sensor_type):
|
||||
def __init__(self, data, name, sensor_unit, sensor_type, device_class):
|
||||
"""Initialize the sensor."""
|
||||
self._data = data
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._type = sensor_type
|
||||
self._unit = sensor_unit
|
||||
self._device_class = device_class
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the sensor."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
|
@ -74,3 +90,5 @@ class DanfossAir(Entity):
|
|||
self._data.update()
|
||||
|
||||
self._state = self._data.get_value(self._type)
|
||||
if self._state is None:
|
||||
_LOGGER.debug("Could not get data for %s", self._type)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
"""Support for the for Danfoss Air HRV sswitches."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDevice)
|
||||
from homeassistant.components.danfoss_air import DOMAIN \
|
||||
as DANFOSS_AIR_DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Danfoss Air HRV switch platform."""
|
||||
from pydanfossair.commands import ReadCommand, UpdateCommand
|
||||
|
||||
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||
|
||||
switches = [
|
||||
["Danfoss Air Boost",
|
||||
ReadCommand.boost,
|
||||
UpdateCommand.boost_activate,
|
||||
UpdateCommand.boost_deactivate],
|
||||
]
|
||||
|
||||
dev = []
|
||||
|
||||
for switch in switches:
|
||||
dev.append(DanfossAir(
|
||||
data, switch[0], switch[1], switch[2], switch[3]))
|
||||
|
||||
add_entities(dev)
|
||||
|
||||
|
||||
class DanfossAir(SwitchDevice):
|
||||
"""Representation of a Danfoss Air HRV Switch."""
|
||||
|
||||
def __init__(self, data, name, state_command, on_command, off_command):
|
||||
"""Initialize the switch."""
|
||||
self._data = data
|
||||
self._name = name
|
||||
self._state_command = state_command
|
||||
self._on_command = on_command
|
||||
self._off_command = off_command
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the switch."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self._state
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
_LOGGER.debug("Turning on switch with command %s", self._on_command)
|
||||
self._data.update_state(self._on_command, self._state_command)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
_LOGGER.debug("Turning of switch with command %s", self._off_command)
|
||||
self._data.update_state(self._off_command, self._state_command)
|
||||
|
||||
def update(self):
|
||||
"""Update the switch's state."""
|
||||
self._data.update()
|
||||
|
||||
self._state = self._data.get_value(self._state_command)
|
||||
if self._state is None:
|
||||
_LOGGER.debug("Could not get data for %s", self._state_command)
|
|
@ -12,7 +12,7 @@
|
|||
"init": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Puerto (valor predeterminado: '80')"
|
||||
"port": "Puerto"
|
||||
},
|
||||
"title": "Definir el gateway deCONZ"
|
||||
},
|
||||
|
@ -23,7 +23,8 @@
|
|||
"data": {
|
||||
"allow_clip_sensor": "Permitir la importaci\u00f3n de sensores virtuales",
|
||||
"allow_deconz_groups": "Permitir la importaci\u00f3n de grupos deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "Opciones de configuraci\u00f3n adicionales para deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ Zigbee gateway"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"init": {
|
||||
"data": {
|
||||
"host": "H\u00e1zigazda (Host)",
|
||||
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa"
|
||||
},
|
||||
|
|
|
@ -28,6 +28,6 @@
|
|||
"title": "Opzioni di configurazione extra per deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "Gateway Zigbee deCONZ"
|
||||
}
|
||||
}
|
|
@ -28,6 +28,6 @@
|
|||
"title": "Extra konfigurationsalternativ f\u00f6r deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee Gateway"
|
||||
}
|
||||
}
|
|
@ -12,10 +12,7 @@ from .config_flow import configured_hosts
|
|||
from .const import DEFAULT_PORT, DOMAIN, _LOGGER
|
||||
from .gateway import DeconzGateway
|
||||
|
||||
REQUIREMENTS = ['pydeconz==47']
|
||||
|
||||
SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
|
||||
'light', 'scene', 'sensor', 'switch']
|
||||
REQUIREMENTS = ['pydeconz==52']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
|
@ -71,11 +68,11 @@ async def async_setup_entry(hass, config_entry):
|
|||
|
||||
gateway = DeconzGateway(hass, config_entry)
|
||||
|
||||
hass.data[DOMAIN] = gateway
|
||||
|
||||
if not await gateway.async_setup():
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = gateway
|
||||
|
||||
device_registry = await \
|
||||
hass.helpers.device_registry.async_get_registry()
|
||||
device_registry.async_get_or_create(
|
||||
|
|
|
@ -5,7 +5,8 @@ from homeassistant.core import callback
|
|||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN)
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
|
||||
NEW_SENSOR)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -34,7 +35,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
"""Support for deCONZ climate devices."""
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_OFFSET, ATTR_VALVE, CONF_ALLOW_CLIP_SENSOR,
|
||||
DOMAIN as DECONZ_DOMAIN, NEW_SENSOR)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ climate devices.
|
||||
|
||||
Thermostats are based on the same device class as sensors in deCONZ.
|
||||
"""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_climate(sensors):
|
||||
"""Add climate devices from deCONZ."""
|
||||
from pydeconz.sensor import THERMOSTAT
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
if sensor.type in THERMOSTAT and \
|
||||
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
|
||||
entities.append(DeconzThermostat(sensor, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, NEW_SENSOR, async_add_climate))
|
||||
|
||||
async_add_climate(gateway.api.sensors.values())
|
||||
|
||||
|
||||
class DeconzThermostat(DeconzDevice, ClimateDevice):
|
||||
"""Representation of a deCONZ thermostat."""
|
||||
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up thermostat device."""
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._features = SUPPORT_ON_OFF
|
||||
self._features |= SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._device.on
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on switch."""
|
||||
data = {'mode': 'auto'}
|
||||
await self._device.async_set_config(data)
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off switch."""
|
||||
data = {'mode': 'off'}
|
||||
await self._device.async_set_config(data)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._device.temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
return self._device.heatsetpoint
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
data = {}
|
||||
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
data['heatsetpoint'] = kwargs[ATTR_TEMPERATURE] * 100
|
||||
|
||||
await self._device.async_set_config(data)
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the thermostat."""
|
||||
attr = {}
|
||||
|
||||
if self._device.battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self._device.battery
|
||||
|
||||
if self._device.offset:
|
||||
attr[ATTR_OFFSET] = self._device.offset
|
||||
|
||||
if self._device.valve is not None:
|
||||
attr[ATTR_VALVE] = self._device.valve
|
||||
|
||||
return attr
|
|
@ -10,13 +10,27 @@ DEFAULT_PORT = 80
|
|||
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
|
||||
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
|
||||
|
||||
SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
|
||||
SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover',
|
||||
'light', 'scene', 'sensor', 'switch']
|
||||
|
||||
DECONZ_REACHABLE = 'deconz_reachable'
|
||||
|
||||
NEW_GROUP = 'deconz_new_group'
|
||||
NEW_LIGHT = 'deconz_new_light'
|
||||
NEW_SCENE = 'deconz_new_scene'
|
||||
NEW_SENSOR = 'deconz_new_sensor'
|
||||
|
||||
NEW_DEVICE = {
|
||||
'group': NEW_GROUP,
|
||||
'light': NEW_LIGHT,
|
||||
'scene': NEW_SCENE,
|
||||
'sensor': NEW_SENSOR
|
||||
}
|
||||
|
||||
ATTR_DARK = 'dark'
|
||||
ATTR_OFFSET = 'offset'
|
||||
ATTR_ON = 'on'
|
||||
ATTR_VALVE = 'valve'
|
||||
|
||||
DAMPERS = ["Level controllable output"]
|
||||
WINDOW_COVERS = ["Window covering device"]
|
||||
|
|
|
@ -5,7 +5,8 @@ from homeassistant.components.cover import (
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, WINDOW_COVERS
|
||||
from .const import (
|
||||
COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, WINDOW_COVERS)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -39,7 +40,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover))
|
||||
async_dispatcher_connect(hass, NEW_LIGHT, async_add_cover))
|
||||
|
||||
async_add_cover(gateway.api.lights.values())
|
||||
|
||||
|
@ -48,7 +49,7 @@ class DeconzCover(DeconzDevice, CoverDevice):
|
|||
"""Representation of a deCONZ cover."""
|
||||
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up cover and add update callback to get data from websocket."""
|
||||
"""Set up cover device."""
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._features = SUPPORT_OPEN
|
||||
|
|
|
@ -8,7 +8,8 @@ from homeassistant.helpers.dispatcher import (
|
|||
from homeassistant.util import slugify
|
||||
|
||||
from .const import (
|
||||
DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS)
|
||||
_LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, NEW_DEVICE, NEW_SENSOR,
|
||||
SUPPORTED_PLATFORMS)
|
||||
|
||||
|
||||
class DeconzGateway:
|
||||
|
@ -44,7 +45,7 @@ class DeconzGateway:
|
|||
|
||||
self.listeners.append(
|
||||
async_dispatcher_connect(
|
||||
hass, 'deconz_new_sensor', self.async_add_remote))
|
||||
hass, NEW_SENSOR, self.async_add_remote))
|
||||
|
||||
self.async_add_remote(self.api.sensors.values())
|
||||
|
||||
|
@ -64,8 +65,7 @@ class DeconzGateway:
|
|||
"""Handle event of new device creation in deCONZ."""
|
||||
if not isinstance(device, list):
|
||||
device = [device]
|
||||
async_dispatcher_send(
|
||||
self.hass, 'deconz_new_{}'.format(device_type), device)
|
||||
async_dispatcher_send(self.hass, NEW_DEVICE[device_type], device)
|
||||
|
||||
@callback
|
||||
def async_add_remote(self, sensors):
|
||||
|
@ -140,6 +140,7 @@ class DeconzEvent:
|
|||
self._device.register_async_callback(self.async_update_callback)
|
||||
self._event = 'deconz_{}'.format(CONF_EVENT)
|
||||
self._id = slugify(self._device.name)
|
||||
_LOGGER.debug("deCONZ event created: %s", self._id)
|
||||
|
||||
@callback
|
||||
def async_will_remove_from_hass(self) -> None:
|
||||
|
|
|
@ -9,8 +9,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
import homeassistant.util.color as color_util
|
||||
|
||||
from .const import (
|
||||
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES,
|
||||
SWITCH_TYPES)
|
||||
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES, NEW_GROUP,
|
||||
NEW_LIGHT, SWITCH_TYPES)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -36,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_light', async_add_light))
|
||||
async_dispatcher_connect(hass, NEW_LIGHT, async_add_light))
|
||||
|
||||
@callback
|
||||
def async_add_group(groups):
|
||||
|
@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_group', async_add_group))
|
||||
async_dispatcher_connect(hass, NEW_GROUP, async_add_group))
|
||||
|
||||
async_add_light(gateway.api.lights.values())
|
||||
async_add_group(gateway.api.groups.values())
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"""Support for deCONZ scenes."""
|
||||
from homeassistant.components.deconz import DOMAIN as DECONZ_DOMAIN
|
||||
from homeassistant.components.scene import Scene
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, NEW_SCENE
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
|
@ -25,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
entities.append(DeconzScene(scene, gateway))
|
||||
async_add_entities(entities)
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_scene', async_add_scene))
|
||||
async_dispatcher_connect(hass, NEW_SCENE, async_add_scene))
|
||||
|
||||
async_add_scene(gateway.api.scenes.values())
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.util import slugify
|
||||
|
||||
from .const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN)
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
|
||||
NEW_SENSOR)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -29,7 +30,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
@callback
|
||||
def async_add_sensor(sensors):
|
||||
"""Add sensors from deCONZ."""
|
||||
from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE
|
||||
from pydeconz.sensor import (
|
||||
DECONZ_SENSOR, SWITCH as DECONZ_REMOTE)
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
|
@ -43,7 +45,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from homeassistant.components.switch import SwitchDevice
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS, SIRENS
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, POWER_PLUGS, SIRENS
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -34,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_light', async_add_switch))
|
||||
async_dispatcher_connect(hass, NEW_LIGHT, async_add_switch))
|
||||
|
||||
async_add_switch(gateway.api.lights.values())
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ DEPENDENCIES = (
|
|||
'history',
|
||||
'logbook',
|
||||
'map',
|
||||
'mobile_app',
|
||||
'person',
|
||||
'script',
|
||||
'sun',
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
"""
|
||||
Provides functionality to turn on lights based on the states.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_sun_light_trigger/
|
||||
"""
|
||||
"""Support to turn on lights based on the states."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
|
|
|
@ -291,7 +291,7 @@ class DeviceTracker:
|
|||
"""
|
||||
if mac is None and dev_id is None:
|
||||
raise HomeAssistantError('Neither mac or device id passed in')
|
||||
elif mac is not None:
|
||||
if mac is not None:
|
||||
mac = str(mac).upper()
|
||||
device = self.mac_to_dev.get(mac)
|
||||
if not device:
|
||||
|
@ -580,6 +580,7 @@ class Device(RestoreEntity):
|
|||
return
|
||||
self._state = state.state
|
||||
self.last_update_home = (state.state == STATE_HOME)
|
||||
self.last_seen = dt_util.utcnow()
|
||||
|
||||
for attr, var in (
|
||||
(ATTR_SOURCE_TYPE, 'source_type'),
|
||||
|
|
|
@ -61,8 +61,9 @@ class GoogleMapsScanner:
|
|||
self.max_gps_accuracy = config[CONF_MAX_GPS_ACCURACY]
|
||||
|
||||
try:
|
||||
self.service = Service(self.username, self.password,
|
||||
hass.config.path(CREDENTIALS_FILE))
|
||||
credfile = "{}.{}".format(hass.config.path(CREDENTIALS_FILE),
|
||||
slugify(self.username))
|
||||
self.service = Service(self.username, self.password, credfile)
|
||||
self._update_info()
|
||||
|
||||
track_time_interval(
|
||||
|
|
|
@ -4,21 +4,17 @@ Support for OpenWRT (luci) routers.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.luci/
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.components.device_tracker import (
|
||||
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL)
|
||||
|
||||
REQUIREMENTS = ['openwrt-luci-rpc==1.0.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SSL = False
|
||||
|
@ -31,12 +27,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
})
|
||||
|
||||
|
||||
class InvalidLuciTokenError(HomeAssistantError):
|
||||
"""When an invalid token is detected."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
"""Validate the configuration and return a Luci scanner."""
|
||||
scanner = LuciDeviceScanner(config[DOMAIN])
|
||||
|
@ -44,138 +34,58 @@ def get_scanner(hass, config):
|
|||
return scanner if scanner.success_init else None
|
||||
|
||||
|
||||
Device = namedtuple('Device', ['mac', 'ip', 'flags', 'device', 'host'])
|
||||
|
||||
|
||||
class LuciDeviceScanner(DeviceScanner):
|
||||
"""This class queries a wireless router running OpenWrt firmware."""
|
||||
"""This class scans for devices connected to an OpenWrt router."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
protocol = 'http' if not config[CONF_SSL] else 'https'
|
||||
self.origin = '{}://{}'.format(protocol, self.host)
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
from openwrt_luci_rpc import OpenWrtRpc
|
||||
|
||||
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
|
||||
self.router = OpenWrtRpc(config[CONF_HOST],
|
||||
config[CONF_USERNAME],
|
||||
config[CONF_PASSWORD],
|
||||
config[CONF_SSL])
|
||||
|
||||
self.last_results = {}
|
||||
self.refresh_token()
|
||||
self.mac2name = None
|
||||
self.success_init = self.token is not None
|
||||
|
||||
def refresh_token(self):
|
||||
"""Get a new token."""
|
||||
self.token = _get_token(self.origin, self.username, self.password)
|
||||
self.success_init = self.router.is_logged_in()
|
||||
|
||||
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."""
|
||||
if self.mac2name is None:
|
||||
url = '{}/cgi-bin/luci/rpc/uci'.format(self.origin)
|
||||
result = _req_json_rpc(
|
||||
url, 'get_all', 'dhcp', params={'auth': self.token})
|
||||
if result:
|
||||
hosts = [x for x in result.values()
|
||||
if x['.type'] == 'host' and
|
||||
'mac' in x and 'name' in x]
|
||||
mac2name_list = [
|
||||
(x['mac'].upper(), x['name']) for x in hosts]
|
||||
self.mac2name = dict(mac2name_list)
|
||||
else:
|
||||
# Error, handled in the _req_json_rpc
|
||||
return
|
||||
return self.mac2name.get(device.upper(), None)
|
||||
name = next((
|
||||
result.hostname for result in self.last_results
|
||||
if result.mac == device), None)
|
||||
return name
|
||||
|
||||
def get_extra_attributes(self, device):
|
||||
"""Return the IP of the given device."""
|
||||
filter_att = next((
|
||||
{
|
||||
'ip': result.ip,
|
||||
'flags': result.flags,
|
||||
'device': result.device,
|
||||
'host': result.host
|
||||
} for result in self.last_results
|
||||
"""
|
||||
Get extra attributes of a device.
|
||||
|
||||
Some known extra attributes that may be returned in the device tuple
|
||||
include Mac Address (mac), Network Device (dev), Ip Address
|
||||
(ip), reachable status (reachable), Associated router
|
||||
(host), Hostname if known (hostname) among others.
|
||||
"""
|
||||
device = next((
|
||||
result for result in self.last_results
|
||||
if result.mac == device), None)
|
||||
return filter_att
|
||||
return device._asdict()
|
||||
|
||||
def _update_info(self):
|
||||
"""Ensure the information from the Luci router is up to date.
|
||||
"""Check the Luci router for devices."""
|
||||
result = self.router.get_all_connected_devices(
|
||||
only_reachable=True)
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
_LOGGER.debug("Luci get_all_connected_devices returned:"
|
||||
" %s", result)
|
||||
|
||||
_LOGGER.info("Checking ARP")
|
||||
last_results = []
|
||||
for device in result:
|
||||
last_results.append(device)
|
||||
|
||||
url = '{}/cgi-bin/luci/rpc/sys'.format(self.origin)
|
||||
|
||||
try:
|
||||
result = _req_json_rpc(
|
||||
url, 'net.arptable', params={'auth': self.token})
|
||||
except InvalidLuciTokenError:
|
||||
_LOGGER.info("Refreshing token")
|
||||
self.refresh_token()
|
||||
return False
|
||||
|
||||
if result:
|
||||
self.last_results = []
|
||||
for device_entry in result:
|
||||
# Check if the Flags for each device contain
|
||||
# NUD_REACHABLE and if so, add it to last_results
|
||||
if int(device_entry['Flags'], 16) & 0x2:
|
||||
self.last_results.append(Device(device_entry['HW address'],
|
||||
device_entry['IP address'],
|
||||
device_entry['Flags'],
|
||||
device_entry['Device'],
|
||||
self.host))
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _req_json_rpc(url, method, *args, **kwargs):
|
||||
"""Perform one JSON RPC operation."""
|
||||
data = json.dumps({'method': method, 'params': args})
|
||||
|
||||
try:
|
||||
res = requests.post(url, data=data, timeout=5, **kwargs)
|
||||
except requests.exceptions.Timeout:
|
||||
_LOGGER.exception("Connection to the router timed out")
|
||||
return
|
||||
if res.status_code == 200:
|
||||
try:
|
||||
result = res.json()
|
||||
except ValueError:
|
||||
# If json decoder could not parse the response
|
||||
_LOGGER.exception("Failed to parse response from luci")
|
||||
return
|
||||
try:
|
||||
return result['result']
|
||||
except KeyError:
|
||||
_LOGGER.exception("No result in response from luci")
|
||||
return
|
||||
elif res.status_code == 401:
|
||||
# Authentication error
|
||||
_LOGGER.exception(
|
||||
"Failed to authenticate, check your username and password")
|
||||
return
|
||||
elif res.status_code == 403:
|
||||
_LOGGER.error("Luci responded with a 403 Invalid token")
|
||||
raise InvalidLuciTokenError
|
||||
|
||||
else:
|
||||
_LOGGER.error("Invalid response from luci: %s", res)
|
||||
|
||||
|
||||
def _get_token(origin, username, password):
|
||||
"""Get authentication token for the given configuration."""
|
||||
url = '{}/cgi-bin/luci/rpc/auth'.format(origin)
|
||||
return _req_json_rpc(url, 'login', username, password)
|
||||
self.last_results = last_results
|
||||
|
|
|
@ -13,7 +13,7 @@ from homeassistant.const import (
|
|||
CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_PORT, CONF_SSL, CONF_VERIFY_SSL)
|
||||
|
||||
REQUIREMENTS = ['synology-srm==0.0.4']
|
||||
REQUIREMENTS = ['synology-srm==0.0.6']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -12,14 +12,15 @@ import voluptuous as vol
|
|||
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL,
|
||||
CONF_PASSWORD, CONF_USERNAME, ATTR_BATTERY_LEVEL)
|
||||
CONF_PASSWORD, CONF_USERNAME, ATTR_BATTERY_LEVEL,
|
||||
CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.util import slugify
|
||||
|
||||
|
||||
REQUIREMENTS = ['pytraccar==0.2.1']
|
||||
REQUIREMENTS = ['pytraccar==0.3.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -31,6 +32,7 @@ ATTR_SPEED = 'speed'
|
|||
ATTR_TRACKER = 'tracker'
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
|
||||
SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
|
@ -39,6 +41,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_PORT, default=8082): cv.port,
|
||||
vol.Optional(CONF_SSL, default=False): cv.boolean,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS,
|
||||
default=[]): vol.All(cv.ensure_list, [cv.string]),
|
||||
})
|
||||
|
||||
|
||||
|
@ -50,15 +54,22 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
|||
|
||||
api = API(hass.loop, session, config[CONF_USERNAME], config[CONF_PASSWORD],
|
||||
config[CONF_HOST], config[CONF_PORT], config[CONF_SSL])
|
||||
scanner = TraccarScanner(api, hass, async_see)
|
||||
|
||||
scanner = TraccarScanner(
|
||||
api, hass, async_see,
|
||||
config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL),
|
||||
config[CONF_MONITORED_CONDITIONS])
|
||||
|
||||
return await scanner.async_init()
|
||||
|
||||
|
||||
class TraccarScanner:
|
||||
"""Define an object to retrieve Traccar data."""
|
||||
|
||||
def __init__(self, api, hass, async_see):
|
||||
def __init__(self, api, hass, async_see, scan_interval, custom_attributes):
|
||||
"""Initialize."""
|
||||
self._custom_attributes = custom_attributes
|
||||
self._scan_interval = scan_interval
|
||||
self._async_see = async_see
|
||||
self._api = api
|
||||
self._hass = hass
|
||||
|
@ -70,14 +81,14 @@ class TraccarScanner:
|
|||
await self._async_update()
|
||||
async_track_time_interval(self._hass,
|
||||
self._async_update,
|
||||
DEFAULT_SCAN_INTERVAL)
|
||||
self._scan_interval)
|
||||
|
||||
return self._api.authenticated
|
||||
|
||||
async def _async_update(self, now=None):
|
||||
"""Update info from Traccar."""
|
||||
_LOGGER.debug('Updating device data.')
|
||||
await self._api.get_device_info()
|
||||
await self._api.get_device_info(self._custom_attributes)
|
||||
for devicename in self._api.device_info:
|
||||
device = self._api.device_info[devicename]
|
||||
attr = {}
|
||||
|
@ -94,6 +105,9 @@ class TraccarScanner:
|
|||
attr[ATTR_BATTERY_LEVEL] = device['battery']
|
||||
if device.get('motion') is not None:
|
||||
attr[ATTR_MOTION] = device['motion']
|
||||
for custom_attr in self._custom_attributes:
|
||||
if device.get(custom_attr) is not None:
|
||||
attr[custom_attr] = device[custom_attr]
|
||||
await self._async_see(
|
||||
dev_id=slugify(device['device_id']),
|
||||
gps=(device.get('latitude'), device.get('longitude')),
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Support for Ubee router.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.ubee/
|
||||
"""
|
||||
|
||||
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 = ['pyubee==0.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
"""Validate the configuration and return a Ubee scanner."""
|
||||
try:
|
||||
return UbeeDeviceScanner(config[DOMAIN])
|
||||
except ConnectionError:
|
||||
return None
|
||||
|
||||
|
||||
class UbeeDeviceScanner(DeviceScanner):
|
||||
"""This class queries a wireless Ubee router."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the Ubee scanner."""
|
||||
from pyubee import Ubee
|
||||
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
|
||||
self.last_results = {}
|
||||
self.mac2name = {}
|
||||
|
||||
self.ubee = Ubee(self.host, self.username, self.password)
|
||||
_LOGGER.info("Logging in")
|
||||
results = self.get_connected_devices()
|
||||
self.success_init = results is not None
|
||||
|
||||
if self.success_init:
|
||||
self.last_results = results
|
||||
else:
|
||||
_LOGGER.error("Login failed")
|
||||
|
||||
def scan_devices(self):
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return self.last_results
|
||||
|
||||
def get_device_name(self, device):
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if device in self.mac2name:
|
||||
return self.mac2name.get(device)
|
||||
|
||||
return None
|
||||
|
||||
def _update_info(self):
|
||||
"""Retrieve latest information from the Ubee router."""
|
||||
if not self.success_init:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Scanning")
|
||||
results = self.get_connected_devices()
|
||||
|
||||
if results is None:
|
||||
_LOGGER.warning("Error scanning devices")
|
||||
return
|
||||
|
||||
self.last_results = results or []
|
||||
|
||||
def get_connected_devices(self):
|
||||
"""List connected devices with pyubee."""
|
||||
if not self.ubee.session_active():
|
||||
self.ubee.login()
|
||||
|
||||
return self.ubee.get_connected_devices()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue