Merge pull request #21712 from home-assistant/rc

0.89.0
pull/21986/head 0.89.0
Paulus Schoutsen 2019-03-06 15:15:37 -08:00 committed by GitHub
commit 88bc3033d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
796 changed files with 16058 additions and 5070 deletions

View File

@ -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

View File

@ -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!

View File

@ -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!

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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."""

View File

@ -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."""

View File

@ -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:

View File

@ -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) \

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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")

View File

@ -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,
})

View File

@ -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:

View File

@ -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__)

View File

@ -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':

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,9 @@
{
"config": {
"step": {
"user": {
"title": "Completa tu informaci\u00f3n"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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)"
}
}

View File

@ -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')

View File

@ -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'

View File

@ -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
}

View File

@ -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':

View File

@ -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"
}
},

View File

@ -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"

View File

@ -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:",

View File

@ -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"
}
},

View File

@ -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):

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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__)

View File

@ -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,
}

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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']

View File

@ -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']

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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 (

View File

@ -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
)

View File

@ -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 (

View File

@ -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,

View File

@ -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']

View File

@ -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']

View File

@ -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']

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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,
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
},

View File

@ -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"
}
}

View File

@ -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__)

View File

@ -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")

View File

@ -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."""

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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"
},

View File

@ -28,6 +28,6 @@
"title": "Opzioni di configurazione extra per deCONZ"
}
},
"title": "deCONZ"
"title": "Gateway Zigbee deCONZ"
}
}

View File

@ -28,6 +28,6 @@
"title": "Extra konfigurationsalternativ f\u00f6r deCONZ"
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee Gateway"
}
}

View File

@ -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(

View File

@ -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())

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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:

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -11,6 +11,7 @@ DEPENDENCIES = (
'history',
'logbook',
'map',
'mobile_app',
'person',
'script',
'sun',

View File

@ -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

View File

@ -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'),

View File

@ -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(

View File

@ -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

View File

@ -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__)

View File

@ -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')),

View File

@ -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