mirror of
https://github.com/home-assistant/core.git
synced 2026-01-04 23:05:26 +01:00
Compare commits
172 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f12b37977 | ||
|
|
a2a4c633f3 | ||
|
|
e6dd4f6e13 | ||
|
|
b327ea2023 | ||
|
|
a1d8b0e9b3 | ||
|
|
1e7cfc04af | ||
|
|
46de89e1a3 | ||
|
|
852526e10a | ||
|
|
07dde62e70 | ||
|
|
cb458b7745 | ||
|
|
b82371f44b | ||
|
|
1c525968d1 | ||
|
|
d7fd9247a9 | ||
|
|
0dc155c4d3 | ||
|
|
c39e6b9618 | ||
|
|
855cbc0aed | ||
|
|
3c3a53a137 | ||
|
|
63b28aa39d | ||
|
|
279fd39677 | ||
|
|
c978281d1e | ||
|
|
311a44007c | ||
|
|
47401739ea | ||
|
|
11ba7cc8ce | ||
|
|
c3ad30ec87 | ||
|
|
5d6db9a915 | ||
|
|
56bbadb501 | ||
|
|
fa79aead9a | ||
|
|
24fec3e826 | ||
|
|
2524dca7bf | ||
|
|
56f17b8651 | ||
|
|
49623d2dad | ||
|
|
66479dc2e5 | ||
|
|
bbbec5a056 | ||
|
|
94b55efef3 | ||
|
|
fd38caa287 | ||
|
|
c61a652c90 | ||
|
|
e3e014bccc | ||
|
|
26590e244c | ||
|
|
39971ee919 | ||
|
|
2205090795 | ||
|
|
a277470363 | ||
|
|
19f2bbf52f | ||
|
|
dbb786c548 | ||
|
|
4fbe3bb070 | ||
|
|
9066ac44fe | ||
|
|
742144f401 | ||
|
|
c0b6a857f7 | ||
|
|
d6dee62c92 | ||
|
|
41017f10a3 | ||
|
|
ba50a5c329 | ||
|
|
4208bb457d | ||
|
|
15af6b1ad9 | ||
|
|
3921dc77a6 | ||
|
|
0094fd5c34 | ||
|
|
d58e401812 | ||
|
|
c79c94550f | ||
|
|
9b950f5192 | ||
|
|
2520fddbdf | ||
|
|
3f21966ec9 | ||
|
|
69502163bd | ||
|
|
893e0f8db6 | ||
|
|
1c8b52f630 | ||
|
|
6e4fb7a937 | ||
|
|
ab1939f56f | ||
|
|
15507df407 | ||
|
|
46ea28a4f8 | ||
|
|
c8458fd7c5 | ||
|
|
e681a7929c | ||
|
|
b2d37ccef6 | ||
|
|
9dd2c36de4 | ||
|
|
b92350fb55 | ||
|
|
6c0fc65eaf | ||
|
|
42ba2a68ce | ||
|
|
508d0459a7 | ||
|
|
dbae410cf4 | ||
|
|
ae51dc08bf | ||
|
|
672a3c7178 | ||
|
|
f8bc3411ad | ||
|
|
038168c417 | ||
|
|
73034c933e | ||
|
|
d3ceb9080c | ||
|
|
3893d8a876 | ||
|
|
05924a2868 | ||
|
|
021d08a9c4 | ||
|
|
5a71a22fb9 | ||
|
|
6064932e2e | ||
|
|
9de7034d0e | ||
|
|
96d5684a89 | ||
|
|
91962e2681 | ||
|
|
ee31f89049 | ||
|
|
370c3f28b8 | ||
|
|
66110a7d57 | ||
|
|
c419cbb46f | ||
|
|
a02d7989d5 | ||
|
|
7325847fa9 | ||
|
|
124495dd84 | ||
|
|
0c01f3a0fe | ||
|
|
0ea2d99910 | ||
|
|
6456f66b47 | ||
|
|
94eee6d069 | ||
|
|
6e5a2a77ab | ||
|
|
35b609dd8b | ||
|
|
0df99f8762 | ||
|
|
6781ecf159 | ||
|
|
a4b843eb2d | ||
|
|
302717e8a1 | ||
|
|
617647c5fd | ||
|
|
4b5d578c08 | ||
|
|
bfc55137ea | ||
|
|
e98e7e2751 | ||
|
|
b687de879c | ||
|
|
4048ad36a8 | ||
|
|
8c2f0e3b30 | ||
|
|
6cabbd2592 | ||
|
|
8d22754a06 | ||
|
|
be6d1b5e94 | ||
|
|
c84f1d7d33 | ||
|
|
49845d9398 | ||
|
|
895306f822 | ||
|
|
a729742757 | ||
|
|
6bc03ee763 | ||
|
|
75580dfade | ||
|
|
1f8699d9b4 | ||
|
|
fca5d55b43 | ||
|
|
659616a4eb | ||
|
|
3b4f7b4f5d | ||
|
|
62432ced90 | ||
|
|
27873b4457 | ||
|
|
1e7333eeb6 | ||
|
|
7cd620d30f | ||
|
|
7a180ac205 | ||
|
|
153ccda853 | ||
|
|
067e4f6d9a | ||
|
|
9d6ce609f9 | ||
|
|
9800b74a6d | ||
|
|
ef5b2a2492 | ||
|
|
60179a1cbb | ||
|
|
e0cea2d18d | ||
|
|
e29dfa8609 | ||
|
|
ef39bca52e | ||
|
|
5a3ea74a26 | ||
|
|
8869617890 | ||
|
|
62f970e486 | ||
|
|
f9a21dbfda | ||
|
|
86c6b4d8e3 | ||
|
|
7bfa81c592 | ||
|
|
d07e40c483 | ||
|
|
0e7e58f172 | ||
|
|
cbdfc95cc8 | ||
|
|
1642502a70 | ||
|
|
33ebd99068 | ||
|
|
9c17e95fc5 | ||
|
|
1533bc1e1f | ||
|
|
da3695dccc | ||
|
|
40c8f5f70e | ||
|
|
3ceee66e1b | ||
|
|
6b908b6f4e | ||
|
|
a74b081d44 | ||
|
|
bc8093c73b | ||
|
|
ca2712506b | ||
|
|
c871e8da5d | ||
|
|
722c27f1e2 | ||
|
|
e3fcf46566 | ||
|
|
1117371b31 | ||
|
|
addca54118 | ||
|
|
3db5d5bbf9 | ||
|
|
1375adfeab | ||
|
|
00cbdffa12 | ||
|
|
c5f012c85a | ||
|
|
656eae288e | ||
|
|
5898307715 | ||
|
|
9b0efdc8c8 |
@@ -192,7 +192,7 @@ omit =
|
||||
homeassistant/components/mychevy.py
|
||||
homeassistant/components/*/mychevy.py
|
||||
|
||||
homeassistant/components/mysensors.py
|
||||
homeassistant/components/mysensors/*
|
||||
homeassistant/components/*/mysensors.py
|
||||
|
||||
homeassistant/components/neato.py
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -107,3 +107,6 @@ desktop.ini
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
|
||||
# monkeytype
|
||||
monkeytype.sqlite3
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
"""Provide an authentication layer for Home Assistant."""
|
||||
import asyncio
|
||||
import binascii
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant import data_entry_flow, requirements
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
|
||||
from homeassistant.util.decorator import Registry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = 'auth'
|
||||
|
||||
AUTH_PROVIDERS = Registry()
|
||||
|
||||
@@ -121,23 +122,12 @@ class User:
|
||||
is_owner = attr.ib(type=bool, default=False)
|
||||
is_active = attr.ib(type=bool, default=False)
|
||||
name = attr.ib(type=str, default=None)
|
||||
# For persisting and see if saved?
|
||||
# store = attr.ib(type=AuthStore, default=None)
|
||||
|
||||
# List of credentials of a user.
|
||||
credentials = attr.ib(type=list, default=attr.Factory(list))
|
||||
credentials = attr.ib(type=list, default=attr.Factory(list), cmp=False)
|
||||
|
||||
# Tokens associated with a user.
|
||||
refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict))
|
||||
|
||||
def as_dict(self):
|
||||
"""Convert user object to a dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'is_owner': self.is_owner,
|
||||
'is_active': self.is_active,
|
||||
'name': self.name,
|
||||
}
|
||||
refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
@@ -152,7 +142,7 @@ class RefreshToken:
|
||||
default=ACCESS_TOKEN_EXPIRATION)
|
||||
token = attr.ib(type=str,
|
||||
default=attr.Factory(lambda: generate_secret(64)))
|
||||
access_tokens = attr.ib(type=list, default=attr.Factory(list))
|
||||
access_tokens = attr.ib(type=list, default=attr.Factory(list), cmp=False)
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
@@ -168,9 +158,10 @@ class AccessToken:
|
||||
default=attr.Factory(generate_secret))
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
"""Return datetime when this token expires."""
|
||||
return self.created_at + self.refresh_token.access_token_expiration
|
||||
def expired(self):
|
||||
"""Return if this token has expired."""
|
||||
expires = self.created_at + self.refresh_token.access_token_expiration
|
||||
return dt_util.utcnow() > expires
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
@@ -281,7 +272,24 @@ class AuthManager:
|
||||
self.login_flow = data_entry_flow.FlowManager(
|
||||
hass, self._async_create_login_flow,
|
||||
self._async_finish_login_flow)
|
||||
self.access_tokens = {}
|
||||
self._access_tokens = {}
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""Return if any auth providers are registered."""
|
||||
return bool(self._providers)
|
||||
|
||||
@property
|
||||
def support_legacy(self):
|
||||
"""
|
||||
Return if legacy_api_password auth providers are registered.
|
||||
|
||||
Should be removed when we removed legacy_api_password auth providers.
|
||||
"""
|
||||
for provider_type, _ in self._providers:
|
||||
if provider_type == 'legacy_api_password':
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def async_auth_providers(self):
|
||||
@@ -317,13 +325,22 @@ class AuthManager:
|
||||
def async_create_access_token(self, refresh_token):
|
||||
"""Create a new access token."""
|
||||
access_token = AccessToken(refresh_token)
|
||||
self.access_tokens[access_token.token] = access_token
|
||||
self._access_tokens[access_token.token] = access_token
|
||||
return access_token
|
||||
|
||||
@callback
|
||||
def async_get_access_token(self, token):
|
||||
"""Get an access token."""
|
||||
return self.access_tokens.get(token)
|
||||
tkn = self._access_tokens.get(token)
|
||||
|
||||
if tkn is None:
|
||||
return None
|
||||
|
||||
if tkn.expired:
|
||||
self._access_tokens.pop(token)
|
||||
return None
|
||||
|
||||
return tkn
|
||||
|
||||
async def async_create_client(self, name, *, redirect_uris=None,
|
||||
no_secret=False):
|
||||
@@ -331,6 +348,16 @@ class AuthManager:
|
||||
return await self._store.async_create_client(
|
||||
name, redirect_uris, no_secret)
|
||||
|
||||
async def async_get_or_create_client(self, name, *, redirect_uris=None,
|
||||
no_secret=False):
|
||||
"""Find a client, if not exists, create a new one."""
|
||||
for client in await self._store.async_get_clients():
|
||||
if client.name == name:
|
||||
return client
|
||||
|
||||
return await self._store.async_create_client(
|
||||
name, redirect_uris, no_secret)
|
||||
|
||||
async def async_get_client(self, client_id):
|
||||
"""Get a client."""
|
||||
return await self._store.async_get_client(client_id)
|
||||
@@ -374,29 +401,36 @@ class AuthStore:
|
||||
def __init__(self, hass):
|
||||
"""Initialize the auth store."""
|
||||
self.hass = hass
|
||||
self.users = None
|
||||
self.clients = None
|
||||
self._load_lock = asyncio.Lock(loop=hass.loop)
|
||||
self._users = None
|
||||
self._clients = None
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
|
||||
async def credentials_for_provider(self, provider_type, provider_id):
|
||||
"""Return credentials for specific auth provider type and id."""
|
||||
if self.users is None:
|
||||
if self._users is None:
|
||||
await self.async_load()
|
||||
|
||||
return [
|
||||
credentials
|
||||
for user in self.users.values()
|
||||
for user in self._users.values()
|
||||
for credentials in user.credentials
|
||||
if (credentials.auth_provider_type == provider_type and
|
||||
credentials.auth_provider_id == provider_id)
|
||||
]
|
||||
|
||||
async def async_get_user(self, user_id):
|
||||
"""Retrieve a user."""
|
||||
if self.users is None:
|
||||
async def async_get_users(self):
|
||||
"""Retrieve all users."""
|
||||
if self._users is None:
|
||||
await self.async_load()
|
||||
|
||||
return self.users.get(user_id)
|
||||
return list(self._users.values())
|
||||
|
||||
async def async_get_user(self, user_id):
|
||||
"""Retrieve a user."""
|
||||
if self._users is None:
|
||||
await self.async_load()
|
||||
|
||||
return self._users.get(user_id)
|
||||
|
||||
async def async_get_or_create_user(self, credentials, auth_provider):
|
||||
"""Get or create a new user for given credentials.
|
||||
@@ -404,7 +438,7 @@ class AuthStore:
|
||||
If link_user is passed in, the credentials will be linked to the passed
|
||||
in user if the credentials are new.
|
||||
"""
|
||||
if self.users is None:
|
||||
if self._users is None:
|
||||
await self.async_load()
|
||||
|
||||
# New credentials, store in user
|
||||
@@ -412,7 +446,7 @@ class AuthStore:
|
||||
info = await auth_provider.async_user_meta_for_credentials(
|
||||
credentials)
|
||||
# Make owner and activate user if it's the first user.
|
||||
if self.users:
|
||||
if self._users:
|
||||
is_owner = False
|
||||
is_active = False
|
||||
else:
|
||||
@@ -424,11 +458,11 @@ class AuthStore:
|
||||
is_active=is_active,
|
||||
name=info.get('name'),
|
||||
)
|
||||
self.users[new_user.id] = new_user
|
||||
self._users[new_user.id] = new_user
|
||||
await self.async_link_user(new_user, credentials)
|
||||
return new_user
|
||||
|
||||
for user in self.users.values():
|
||||
for user in self._users.values():
|
||||
for creds in user.credentials:
|
||||
if (creds.auth_provider_type == credentials.auth_provider_type
|
||||
and creds.auth_provider_id ==
|
||||
@@ -445,11 +479,19 @@ class AuthStore:
|
||||
|
||||
async def async_remove_user(self, user):
|
||||
"""Remove a user."""
|
||||
self.users.pop(user.id)
|
||||
self._users.pop(user.id)
|
||||
await self.async_save()
|
||||
|
||||
async def async_create_refresh_token(self, user, client_id):
|
||||
"""Create a new token for a user."""
|
||||
local_user = await self.async_get_user(user.id)
|
||||
if local_user is None:
|
||||
raise ValueError('Invalid user')
|
||||
|
||||
local_client = await self.async_get_client(client_id)
|
||||
if local_client is None:
|
||||
raise ValueError('Invalid client_id')
|
||||
|
||||
refresh_token = RefreshToken(user, client_id)
|
||||
user.refresh_tokens[refresh_token.token] = refresh_token
|
||||
await self.async_save()
|
||||
@@ -457,10 +499,10 @@ class AuthStore:
|
||||
|
||||
async def async_get_refresh_token(self, token):
|
||||
"""Get refresh token by token."""
|
||||
if self.users is None:
|
||||
if self._users is None:
|
||||
await self.async_load()
|
||||
|
||||
for user in self.users.values():
|
||||
for user in self._users.values():
|
||||
refresh_token = user.refresh_tokens.get(token)
|
||||
if refresh_token is not None:
|
||||
return refresh_token
|
||||
@@ -469,7 +511,7 @@ class AuthStore:
|
||||
|
||||
async def async_create_client(self, name, redirect_uris, no_secret):
|
||||
"""Create a new client."""
|
||||
if self.clients is None:
|
||||
if self._clients is None:
|
||||
await self.async_load()
|
||||
|
||||
kwargs = {
|
||||
@@ -481,23 +523,148 @@ class AuthStore:
|
||||
kwargs['secret'] = None
|
||||
|
||||
client = Client(**kwargs)
|
||||
self.clients[client.id] = client
|
||||
self._clients[client.id] = client
|
||||
await self.async_save()
|
||||
return client
|
||||
|
||||
async def async_get_client(self, client_id):
|
||||
"""Get a client."""
|
||||
if self.clients is None:
|
||||
async def async_get_clients(self):
|
||||
"""Return all clients."""
|
||||
if self._clients is None:
|
||||
await self.async_load()
|
||||
|
||||
return self.clients.get(client_id)
|
||||
return list(self._clients.values())
|
||||
|
||||
async def async_get_client(self, client_id):
|
||||
"""Get a client."""
|
||||
if self._clients is None:
|
||||
await self.async_load()
|
||||
|
||||
return self._clients.get(client_id)
|
||||
|
||||
async def async_load(self):
|
||||
"""Load the users."""
|
||||
async with self._load_lock:
|
||||
self.users = {}
|
||||
self.clients = {}
|
||||
data = await self._store.async_load()
|
||||
|
||||
# Make sure that we're not overriding data if 2 loads happened at the
|
||||
# same time
|
||||
if self._users is not None:
|
||||
return
|
||||
|
||||
if data is None:
|
||||
self._users = {}
|
||||
self._clients = {}
|
||||
return
|
||||
|
||||
users = {
|
||||
user_dict['id']: User(**user_dict) for user_dict in data['users']
|
||||
}
|
||||
|
||||
for cred_dict in data['credentials']:
|
||||
users[cred_dict['user_id']].credentials.append(Credentials(
|
||||
id=cred_dict['id'],
|
||||
is_new=False,
|
||||
auth_provider_type=cred_dict['auth_provider_type'],
|
||||
auth_provider_id=cred_dict['auth_provider_id'],
|
||||
data=cred_dict['data'],
|
||||
))
|
||||
|
||||
refresh_tokens = {}
|
||||
|
||||
for rt_dict in data['refresh_tokens']:
|
||||
token = RefreshToken(
|
||||
id=rt_dict['id'],
|
||||
user=users[rt_dict['user_id']],
|
||||
client_id=rt_dict['client_id'],
|
||||
created_at=dt_util.parse_datetime(rt_dict['created_at']),
|
||||
access_token_expiration=timedelta(
|
||||
seconds=rt_dict['access_token_expiration']),
|
||||
token=rt_dict['token'],
|
||||
)
|
||||
refresh_tokens[token.id] = token
|
||||
users[rt_dict['user_id']].refresh_tokens[token.token] = token
|
||||
|
||||
for ac_dict in data['access_tokens']:
|
||||
refresh_token = refresh_tokens[ac_dict['refresh_token_id']]
|
||||
token = AccessToken(
|
||||
refresh_token=refresh_token,
|
||||
created_at=dt_util.parse_datetime(ac_dict['created_at']),
|
||||
token=ac_dict['token'],
|
||||
)
|
||||
refresh_token.access_tokens.append(token)
|
||||
|
||||
clients = {
|
||||
cl_dict['id']: Client(**cl_dict) for cl_dict in data['clients']
|
||||
}
|
||||
|
||||
self._users = users
|
||||
self._clients = clients
|
||||
|
||||
async def async_save(self):
|
||||
"""Save users."""
|
||||
pass
|
||||
users = [
|
||||
{
|
||||
'id': user.id,
|
||||
'is_owner': user.is_owner,
|
||||
'is_active': user.is_active,
|
||||
'name': user.name,
|
||||
}
|
||||
for user in self._users.values()
|
||||
]
|
||||
|
||||
credentials = [
|
||||
{
|
||||
'id': credential.id,
|
||||
'user_id': user.id,
|
||||
'auth_provider_type': credential.auth_provider_type,
|
||||
'auth_provider_id': credential.auth_provider_id,
|
||||
'data': credential.data,
|
||||
}
|
||||
for user in self._users.values()
|
||||
for credential in user.credentials
|
||||
]
|
||||
|
||||
refresh_tokens = [
|
||||
{
|
||||
'id': refresh_token.id,
|
||||
'user_id': user.id,
|
||||
'client_id': refresh_token.client_id,
|
||||
'created_at': refresh_token.created_at.isoformat(),
|
||||
'access_token_expiration':
|
||||
refresh_token.access_token_expiration.total_seconds(),
|
||||
'token': refresh_token.token,
|
||||
}
|
||||
for user in self._users.values()
|
||||
for refresh_token in user.refresh_tokens.values()
|
||||
]
|
||||
|
||||
access_tokens = [
|
||||
{
|
||||
'id': user.id,
|
||||
'refresh_token_id': refresh_token.id,
|
||||
'created_at': access_token.created_at.isoformat(),
|
||||
'token': access_token.token,
|
||||
}
|
||||
for user in self._users.values()
|
||||
for refresh_token in user.refresh_tokens.values()
|
||||
for access_token in refresh_token.access_tokens
|
||||
]
|
||||
|
||||
clients = [
|
||||
{
|
||||
'id': client.id,
|
||||
'name': client.name,
|
||||
'secret': client.secret,
|
||||
'redirect_uris': client.redirect_uris,
|
||||
}
|
||||
for client in self._clients.values()
|
||||
]
|
||||
|
||||
data = {
|
||||
'users': users,
|
||||
'clients': clients,
|
||||
'credentials': credentials,
|
||||
'access_tokens': access_tokens,
|
||||
'refresh_tokens': refresh_tokens,
|
||||
}
|
||||
|
||||
await self._store.async_save(data, delay=1)
|
||||
|
||||
@@ -8,10 +8,10 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import auth, data_entry_flow
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import json
|
||||
|
||||
|
||||
PATH_DATA = '.users.json'
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = 'auth_provider.homeassistant'
|
||||
|
||||
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
|
||||
}, extra=vol.PREVENT_EXTRA)
|
||||
@@ -31,14 +31,22 @@ class InvalidUser(HomeAssistantError):
|
||||
class Data:
|
||||
"""Hold the user data."""
|
||||
|
||||
def __init__(self, path, data):
|
||||
def __init__(self, hass):
|
||||
"""Initialize the user data store."""
|
||||
self.path = path
|
||||
self.hass = hass
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._data = None
|
||||
|
||||
async def async_load(self):
|
||||
"""Load stored data."""
|
||||
data = await self._store.async_load()
|
||||
|
||||
if data is None:
|
||||
data = {
|
||||
'salt': auth.generate_secret(),
|
||||
'users': []
|
||||
}
|
||||
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
@@ -99,14 +107,9 @@ class Data:
|
||||
else:
|
||||
raise InvalidUser
|
||||
|
||||
def save(self):
|
||||
async def async_save(self):
|
||||
"""Save data."""
|
||||
json.save_json(self.path, self._data)
|
||||
|
||||
|
||||
def load_data(path):
|
||||
"""Load auth data."""
|
||||
return Data(path, json.load_json(path, None))
|
||||
await self._store.async_save(self._data)
|
||||
|
||||
|
||||
@auth.AUTH_PROVIDERS.register('homeassistant')
|
||||
@@ -121,12 +124,10 @@ class HassAuthProvider(auth.AuthProvider):
|
||||
|
||||
async def async_validate_login(self, username, password):
|
||||
"""Helper to validate a username and password."""
|
||||
def validate():
|
||||
"""Validate creds."""
|
||||
data = self._auth_data()
|
||||
data.validate_login(username, password)
|
||||
|
||||
await self.hass.async_add_job(validate)
|
||||
data = Data(self.hass)
|
||||
await data.async_load()
|
||||
await self.hass.async_add_executor_job(
|
||||
data.validate_login, username, password)
|
||||
|
||||
async def async_get_or_create_credentials(self, flow_result):
|
||||
"""Get credentials based on the flow result."""
|
||||
@@ -141,10 +142,6 @@ class HassAuthProvider(auth.AuthProvider):
|
||||
'username': username
|
||||
})
|
||||
|
||||
def _auth_data(self):
|
||||
"""Return the auth provider data."""
|
||||
return load_data(self.hass.config.path(PATH_DATA))
|
||||
|
||||
|
||||
class LoginFlow(data_entry_flow.FlowHandler):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
104
homeassistant/auth_providers/legacy_api_password.py
Normal file
104
homeassistant/auth_providers/legacy_api_password.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Support Legacy API password auth provider.
|
||||
|
||||
It will be removed when auth system production ready
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import hmac
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant import auth, data_entry_flow
|
||||
from homeassistant.core import callback
|
||||
|
||||
USER_SCHEMA = vol.Schema({
|
||||
vol.Required('username'): str,
|
||||
})
|
||||
|
||||
|
||||
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
|
||||
}, extra=vol.PREVENT_EXTRA)
|
||||
|
||||
LEGACY_USER = 'homeassistant'
|
||||
|
||||
|
||||
class InvalidAuthError(HomeAssistantError):
|
||||
"""Raised when submitting invalid authentication."""
|
||||
|
||||
|
||||
@auth.AUTH_PROVIDERS.register('legacy_api_password')
|
||||
class LegacyApiPasswordAuthProvider(auth.AuthProvider):
|
||||
"""Example auth provider based on hardcoded usernames and passwords."""
|
||||
|
||||
DEFAULT_TITLE = 'Legacy API Password'
|
||||
|
||||
async def async_credential_flow(self):
|
||||
"""Return a flow to login."""
|
||||
return LoginFlow(self)
|
||||
|
||||
@callback
|
||||
def async_validate_login(self, password):
|
||||
"""Helper to validate a username and password."""
|
||||
if not hasattr(self.hass, 'http'):
|
||||
raise ValueError('http component is not loaded')
|
||||
|
||||
if self.hass.http.api_password is None:
|
||||
raise ValueError('http component is not configured using'
|
||||
' api_password')
|
||||
|
||||
if not hmac.compare_digest(self.hass.http.api_password.encode('utf-8'),
|
||||
password.encode('utf-8')):
|
||||
raise InvalidAuthError
|
||||
|
||||
async def async_get_or_create_credentials(self, flow_result):
|
||||
"""Return LEGACY_USER always."""
|
||||
for credential in await self.async_credentials():
|
||||
if credential.data['username'] == LEGACY_USER:
|
||||
return credential
|
||||
|
||||
return self.async_create_credentials({
|
||||
'username': LEGACY_USER
|
||||
})
|
||||
|
||||
async def async_user_meta_for_credentials(self, credentials):
|
||||
"""
|
||||
Set name as LEGACY_USER always.
|
||||
|
||||
Will be used to populate info when creating a new user.
|
||||
"""
|
||||
return {'name': LEGACY_USER}
|
||||
|
||||
|
||||
class LoginFlow(data_entry_flow.FlowHandler):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
"""Initialize the login flow."""
|
||||
self._auth_provider = auth_provider
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Handle the step of the form."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
try:
|
||||
self._auth_provider.async_validate_login(
|
||||
user_input['password'])
|
||||
except InvalidAuthError:
|
||||
errors['base'] = 'invalid_auth'
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(
|
||||
title=self._auth_provider.name,
|
||||
data={}
|
||||
)
|
||||
|
||||
schema = OrderedDict()
|
||||
schema['password'] = str
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='init',
|
||||
data_schema=vol.Schema(schema),
|
||||
errors=errors,
|
||||
)
|
||||
@@ -123,7 +123,6 @@ async def async_from_config_dict(config: Dict[str, Any],
|
||||
components.update(hass.config_entries.async_domains())
|
||||
|
||||
# setup components
|
||||
# pylint: disable=not-an-iterable
|
||||
res = await core_components.async_setup(hass, config)
|
||||
if not res:
|
||||
_LOGGER.error("Home Assistant core failed to initialize. "
|
||||
|
||||
@@ -154,7 +154,6 @@ def async_setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class AlarmControlPanel(Entity):
|
||||
"""An abstract class for alarm control devices."""
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.const import (
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
|
||||
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
|
||||
MqttAvailability)
|
||||
CONF_RETAIN, MqttAvailability)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -54,6 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
config.get(CONF_COMMAND_TOPIC),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_RETAIN),
|
||||
config.get(CONF_PAYLOAD_DISARM),
|
||||
config.get(CONF_PAYLOAD_ARM_HOME),
|
||||
config.get(CONF_PAYLOAD_ARM_AWAY),
|
||||
@@ -66,9 +67,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
"""Representation of a MQTT alarm status."""
|
||||
|
||||
def __init__(self, name, state_topic, command_topic, qos, payload_disarm,
|
||||
payload_arm_home, payload_arm_away, code, availability_topic,
|
||||
payload_available, payload_not_available):
|
||||
def __init__(self, name, state_topic, command_topic, qos, retain,
|
||||
payload_disarm, payload_arm_home, payload_arm_away, code,
|
||||
availability_topic, payload_available, payload_not_available):
|
||||
"""Init the MQTT Alarm Control Panel."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
@@ -77,6 +78,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self._state_topic = state_topic
|
||||
self._command_topic = command_topic
|
||||
self._qos = qos
|
||||
self._retain = retain
|
||||
self._payload_disarm = payload_disarm
|
||||
self._payload_arm_home = payload_arm_home
|
||||
self._payload_arm_away = payload_arm_away
|
||||
@@ -134,7 +136,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._command_topic, self._payload_disarm, self._qos)
|
||||
self.hass, self._command_topic, self._payload_disarm, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
@@ -145,7 +148,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._command_topic, self._payload_arm_home, self._qos)
|
||||
self.hass, self._command_topic, self._payload_arm_home, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
@@ -156,7 +160,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._command_topic, self._payload_arm_away, self._qos)
|
||||
self.hass, self._command_topic, self._payload_arm_away, self._qos,
|
||||
self._retain)
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
"""Validate given code."""
|
||||
|
||||
@@ -107,7 +107,6 @@ class _DisplayCategory(object):
|
||||
THERMOSTAT = "THERMOSTAT"
|
||||
|
||||
# Indicates the endpoint is a television.
|
||||
# pylint: disable=invalid-name
|
||||
TV = "TV"
|
||||
|
||||
|
||||
@@ -1474,9 +1473,6 @@ async def async_api_set_thermostat_mode(hass, config, request, entity):
|
||||
mode = mode if isinstance(mode, str) else mode['value']
|
||||
|
||||
operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST)
|
||||
# Work around a pylint false positive due to
|
||||
# https://github.com/PyCQA/pylint/issues/1830
|
||||
# pylint: disable=stop-iteration-return
|
||||
ha_mode = next(
|
||||
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
|
||||
None
|
||||
|
||||
@@ -81,7 +81,6 @@ class APIEventStream(HomeAssistantView):
|
||||
|
||||
async def get(self, request):
|
||||
"""Provide a streaming interface for the event bus."""
|
||||
# pylint: disable=no-self-use
|
||||
hass = request.app['hass']
|
||||
stop_obj = object()
|
||||
to_write = asyncio.Queue(loop=hass.loop)
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
REQUIREMENTS = ['pyarlo==0.1.7']
|
||||
REQUIREMENTS = ['pyarlo==0.1.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = 'bbb_gpio'
|
||||
|
||||
|
||||
# pylint: disable=no-member
|
||||
def setup(hass, config):
|
||||
"""Set up the BeagleBone Black GPIO component."""
|
||||
# pylint: disable=import-error
|
||||
@@ -34,41 +33,39 @@ def setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
# noqa: F821
|
||||
|
||||
def setup_output(pin):
|
||||
"""Set up a GPIO as output."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
# pylint: disable=import-error
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.setup(pin, GPIO.OUT)
|
||||
|
||||
|
||||
def setup_input(pin, pull_mode):
|
||||
"""Set up a GPIO as input."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
# pylint: disable=import-error
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.setup(pin, GPIO.IN, # noqa: F821
|
||||
GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821
|
||||
else GPIO.PUD_UP) # noqa: F821
|
||||
GPIO.setup(pin, GPIO.IN,
|
||||
GPIO.PUD_DOWN if pull_mode == 'DOWN'
|
||||
else GPIO.PUD_UP)
|
||||
|
||||
|
||||
def write_output(pin, value):
|
||||
"""Write a value to a GPIO."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
# pylint: disable=import-error
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.output(pin, value)
|
||||
|
||||
|
||||
def read_input(pin):
|
||||
"""Read a value from a GPIO."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
# pylint: disable=import-error
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
return GPIO.input(pin) is GPIO.HIGH
|
||||
|
||||
|
||||
def edge_detect(pin, event_callback, bounce):
|
||||
"""Add detection for RISING and FALLING events."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
# pylint: disable=import-error
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.add_event_detect(
|
||||
pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce)
|
||||
|
||||
@@ -67,7 +67,6 @@ async def async_unload_entry(hass, entry):
|
||||
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class BinarySensorDevice(Entity):
|
||||
"""Represent a binary sensor."""
|
||||
|
||||
|
||||
@@ -124,11 +124,11 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
result['check_control_messages'] = check_control_messages
|
||||
elif self._attribute == 'charging_status':
|
||||
result['charging_status'] = vehicle_state.charging_status.value
|
||||
# pylint: disable=W0212
|
||||
# pylint: disable=protected-access
|
||||
result['last_charging_end_result'] = \
|
||||
vehicle_state._attributes['lastChargingEndResult']
|
||||
if self._attribute == 'connection_status':
|
||||
# pylint: disable=W0212
|
||||
# pylint: disable=protected-access
|
||||
result['connection_status'] = \
|
||||
vehicle_state._attributes['connectionStatus']
|
||||
|
||||
@@ -166,7 +166,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
# device class plug: On means device is plugged in,
|
||||
# Off means device is unplugged
|
||||
if self._attribute == 'connection_status':
|
||||
# pylint: disable=W0212
|
||||
# pylint: disable=protected-access
|
||||
self._state = (vehicle_state._attributes['connectionStatus'] ==
|
||||
'CONNECTED')
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.components.digital_ocean import (
|
||||
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
|
||||
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
|
||||
ATTR_REGION, ATTR_VCPUS, DATA_DIGITAL_OCEAN)
|
||||
ATTR_REGION, ATTR_VCPUS, CONF_ATTRIBUTION, DATA_DIGITAL_OCEAN)
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,6 +76,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the Digital Ocean droplet."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_CREATED_AT: self.data.created_at,
|
||||
ATTR_DROPLET_ID: self.data.id,
|
||||
ATTR_DROPLET_NAME: self.data.name,
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.const import (
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
|
||||
REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4']
|
||||
REQUIREMENTS = ['pyflic-homeassistant==0.4.dev0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ class GC100BinarySensor(BinarySensorDevice):
|
||||
|
||||
def __init__(self, name, port_addr, gc100):
|
||||
"""Initialize the GC100 binary sensor."""
|
||||
# pylint: disable=no-member
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._port_addr = port_addr
|
||||
self._gc100 = gc100
|
||||
|
||||
@@ -8,7 +8,7 @@ https://home-assistant.io/components/binary_sensor.isy994/
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Callable # noqa
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
|
||||
|
||||
@@ -52,19 +52,18 @@ class LinodeBinarySensor(BinarySensorDevice):
|
||||
self._node_id = node_id
|
||||
self._state = None
|
||||
self.data = None
|
||||
self._attrs = {}
|
||||
self._name = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
if self.data is not None:
|
||||
return self.data.label
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
if self.data is not None:
|
||||
return self.data.status == 'running'
|
||||
return False
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@@ -74,8 +73,18 @@ class LinodeBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the Linode Node."""
|
||||
if self.data:
|
||||
return {
|
||||
return self._attrs
|
||||
|
||||
def update(self):
|
||||
"""Update state of sensor."""
|
||||
self._linode.update()
|
||||
if self._linode.data is not None:
|
||||
for node in self._linode.data:
|
||||
if node.id == self._node_id:
|
||||
self.data = node
|
||||
if self.data is not None:
|
||||
self._state = self.data.status == 'running'
|
||||
self._attrs = {
|
||||
ATTR_CREATED: self.data.created,
|
||||
ATTR_NODE_ID: self.data.id,
|
||||
ATTR_NODE_NAME: self.data.label,
|
||||
@@ -85,12 +94,4 @@ class LinodeBinarySensor(BinarySensorDevice):
|
||||
ATTR_REGION: self.data.region.country,
|
||||
ATTR_VCPUS: self.data.specs.vcpus,
|
||||
}
|
||||
return {}
|
||||
|
||||
def update(self):
|
||||
"""Update state of sensor."""
|
||||
self._linode.update()
|
||||
if self._linode.data is not None:
|
||||
for node in self._linode.data:
|
||||
if node.id == self._node_id:
|
||||
self.data = node
|
||||
self._name = self.data.label
|
||||
|
||||
@@ -29,7 +29,8 @@ async def async_setup_platform(
|
||||
async_add_devices=async_add_devices)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
class MySensorsBinarySensor(
|
||||
mysensors.device.MySensorsEntity, BinarySensorDevice):
|
||||
"""Representation of a MySensors Binary Sensor child node."""
|
||||
|
||||
@property
|
||||
|
||||
@@ -31,12 +31,10 @@ CAMERA_BINARY_TYPES = {
|
||||
|
||||
STRUCTURE_BINARY_TYPES = {
|
||||
'away': None,
|
||||
# 'security_state', # pending python-nest update
|
||||
}
|
||||
|
||||
STRUCTURE_BINARY_STATE_MAP = {
|
||||
'away': {'away': True, 'home': False},
|
||||
'security_state': {'deter': True, 'ok': False},
|
||||
}
|
||||
|
||||
_BINARY_TYPES_DEPRECATED = [
|
||||
@@ -135,7 +133,7 @@ class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
|
||||
value = getattr(self.device, self.variable)
|
||||
if self.variable in STRUCTURE_BINARY_TYPES:
|
||||
self._state = bool(STRUCTURE_BINARY_STATE_MAP
|
||||
[self.variable][value])
|
||||
[self.variable].get(value))
|
||||
else:
|
||||
self._state = bool(value)
|
||||
|
||||
|
||||
127
homeassistant/components/binary_sensor/rachio.py
Normal file
127
homeassistant/components/binary_sensor/rachio.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Integration with the Rachio Iro sprinkler system controller.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.rachio/
|
||||
"""
|
||||
from abc import abstractmethod
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
|
||||
KEY_DEVICE_ID,
|
||||
KEY_STATUS,
|
||||
KEY_SUBTYPE,
|
||||
SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
||||
STATUS_OFFLINE,
|
||||
STATUS_ONLINE,
|
||||
SUBTYPE_OFFLINE,
|
||||
SUBTYPE_ONLINE,)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['rachio']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Rachio binary sensors."""
|
||||
devices = []
|
||||
for controller in hass.data[DOMAIN_RACHIO].controllers:
|
||||
devices.append(RachioControllerOnlineBinarySensor(hass, controller))
|
||||
|
||||
add_devices(devices)
|
||||
_LOGGER.info("%d Rachio binary sensor(s) added", len(devices))
|
||||
|
||||
|
||||
class RachioControllerBinarySensor(BinarySensorDevice):
|
||||
"""Represent a binary sensor that reflects a Rachio state."""
|
||||
|
||||
def __init__(self, hass, controller, poll=True):
|
||||
"""Set up a new Rachio controller binary sensor."""
|
||||
self._controller = controller
|
||||
|
||||
if poll:
|
||||
self._state = self._poll_update()
|
||||
else:
|
||||
self._state = None
|
||||
|
||||
dispatcher_connect(hass, SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
||||
self._handle_any_update)
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Declare that this entity pushes its state to HA."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return whether the sensor has a 'true' value."""
|
||||
return self._state
|
||||
|
||||
def _handle_any_update(self, *args, **kwargs) -> None:
|
||||
"""Determine whether an update event applies to this device."""
|
||||
if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
|
||||
# For another device
|
||||
return
|
||||
|
||||
# For this device
|
||||
self._handle_update()
|
||||
|
||||
@abstractmethod
|
||||
def _poll_update(self, data=None) -> bool:
|
||||
"""Request the state from the API."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _handle_update(self, *args, **kwargs) -> None:
|
||||
"""Handle an update to the state of this sensor."""
|
||||
pass
|
||||
|
||||
|
||||
class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
|
||||
"""Represent a binary sensor that reflects if the controller is online."""
|
||||
|
||||
def __init__(self, hass, controller):
|
||||
"""Set up a new Rachio controller online binary sensor."""
|
||||
super().__init__(hass, controller, poll=False)
|
||||
self._state = self._poll_update(controller.init_data)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of this sensor including the controller name."""
|
||||
return "{} online".format(self._controller.name)
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return 'connectivity'
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the name of an icon for this sensor."""
|
||||
return 'mdi:wifi-strength-4' if self.is_on\
|
||||
else 'mdi:wifi-strength-off-outline'
|
||||
|
||||
def _poll_update(self, data=None) -> bool:
|
||||
"""Request the state from the API."""
|
||||
if data is None:
|
||||
data = self._controller.rachio.device.get(
|
||||
self._controller.controller_id)[1]
|
||||
|
||||
if data[KEY_STATUS] == STATUS_ONLINE:
|
||||
return True
|
||||
elif data[KEY_STATUS] == STATUS_OFFLINE:
|
||||
return False
|
||||
else:
|
||||
_LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
|
||||
data[KEY_STATUS])
|
||||
|
||||
def _handle_update(self, *args, **kwargs) -> None:
|
||||
"""Handle an update to the state of this sensor."""
|
||||
if args[0][KEY_SUBTYPE] == SUBTYPE_ONLINE:
|
||||
self._state = True
|
||||
elif args[0][KEY_SUBTYPE] == SUBTYPE_OFFLINE:
|
||||
self._state = False
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
@@ -58,7 +58,6 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
|
||||
|
||||
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
|
||||
"""Initialize the RPi binary sensor."""
|
||||
# pylint: disable=no-member
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._port = port
|
||||
self._pull_mode = pull_mode
|
||||
|
||||
@@ -23,7 +23,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.14.3']
|
||||
REQUIREMENTS = ['numpy==1.14.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ DEPENDENCIES = ['wemo']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=too-many-function-args
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Register discovered WeMo binary sensors."""
|
||||
import pywemo.discovery as discovery
|
||||
|
||||
@@ -89,9 +89,7 @@ class GoogleCalendarData(object):
|
||||
params['timeMin'] = start_date.isoformat('T')
|
||||
params['timeMax'] = end_date.isoformat('T')
|
||||
|
||||
# pylint: disable=no-member
|
||||
events = await hass.async_add_job(service.events)
|
||||
# pylint: enable=no-member
|
||||
result = await hass.async_add_job(events.list(**params).execute)
|
||||
|
||||
items = result.get('items', [])
|
||||
@@ -111,7 +109,7 @@ class GoogleCalendarData(object):
|
||||
service, params = self._prepare_query()
|
||||
params['timeMin'] = dt.now().isoformat('T')
|
||||
|
||||
events = service.events() # pylint: disable=no-member
|
||||
events = service.events()
|
||||
result = events.list(**params).execute()
|
||||
|
||||
items = result.get('items', [])
|
||||
|
||||
@@ -322,6 +322,7 @@ class Camera(Entity):
|
||||
except asyncio.CancelledError:
|
||||
_LOGGER.debug("Stream closed by frontend.")
|
||||
response = None
|
||||
raise
|
||||
|
||||
finally:
|
||||
if response is not None:
|
||||
|
||||
@@ -10,12 +10,13 @@ from datetime import timedelta
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.components.neato import (
|
||||
NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN)
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['neato']
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Neato Camera."""
|
||||
@@ -45,7 +46,6 @@ class NeatoCleaningMap(Camera):
|
||||
self.update()
|
||||
return self._image
|
||||
|
||||
@Throttle(timedelta(seconds=10))
|
||||
def update(self):
|
||||
"""Check the contents of the map list."""
|
||||
self.neato.update_robots()
|
||||
|
||||
@@ -233,6 +233,7 @@ class ProxyCamera(Camera):
|
||||
_LOGGER.debug("Stream closed by frontend.")
|
||||
req.close()
|
||||
response = None
|
||||
raise
|
||||
|
||||
finally:
|
||||
if response is not None:
|
||||
|
||||
@@ -67,8 +67,6 @@ async def async_setup_platform(hass, config, async_add_devices,
|
||||
]
|
||||
|
||||
for cam in config.get(CONF_CAMERAS, []):
|
||||
# https://github.com/PyCQA/pylint/issues/1830
|
||||
# pylint: disable=stop-iteration-return
|
||||
camera = next(
|
||||
(dc for dc in discovered_cameras
|
||||
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)
|
||||
|
||||
@@ -104,27 +104,25 @@ class XiaomiCamera(Camera):
|
||||
|
||||
dirs = [d for d in ftp.nlst() if '.' not in d]
|
||||
if not dirs:
|
||||
if self._model == MODEL_YI:
|
||||
_LOGGER.warning("There don't appear to be any uploaded videos")
|
||||
return False
|
||||
elif self._model == MODEL_XIAOFANG:
|
||||
_LOGGER.warning("There don't appear to be any folders")
|
||||
return False
|
||||
_LOGGER.warning("There don't appear to be any folders")
|
||||
return False
|
||||
|
||||
first_dir = dirs[-1]
|
||||
try:
|
||||
ftp.cwd(first_dir)
|
||||
except error_perm as exc:
|
||||
_LOGGER.error('Unable to find path: %s - %s', first_dir, exc)
|
||||
return False
|
||||
first_dir = dirs[-1]
|
||||
try:
|
||||
ftp.cwd(first_dir)
|
||||
except error_perm as exc:
|
||||
_LOGGER.error('Unable to find path: %s - %s', first_dir, exc)
|
||||
return False
|
||||
|
||||
if self._model == MODEL_XIAOFANG:
|
||||
dirs = [d for d in ftp.nlst() if '.' not in d]
|
||||
if not dirs:
|
||||
_LOGGER.warning("There don't appear to be any uploaded videos")
|
||||
return False
|
||||
|
||||
latest_dir = dirs[-1]
|
||||
ftp.cwd(latest_dir)
|
||||
latest_dir = dirs[-1]
|
||||
ftp.cwd(latest_dir)
|
||||
|
||||
videos = [v for v in ftp.nlst() if '.tmp' not in v]
|
||||
if not videos:
|
||||
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
|
||||
|
||||
@@ -53,7 +53,6 @@ class YiCamera(Camera):
|
||||
"""Initialize."""
|
||||
super().__init__()
|
||||
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
|
||||
self._ftp = None
|
||||
self._last_image = None
|
||||
self._last_url = None
|
||||
self._manager = hass.data[DATA_FFMPEG]
|
||||
@@ -64,8 +63,6 @@ class YiCamera(Camera):
|
||||
self.user = config[CONF_USERNAME]
|
||||
self.passwd = config[CONF_PASSWORD]
|
||||
|
||||
hass.async_add_job(self._connect_to_client)
|
||||
|
||||
@property
|
||||
def brand(self):
|
||||
"""Camera brand."""
|
||||
@@ -76,38 +73,35 @@ class YiCamera(Camera):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
async def _connect_to_client(self):
|
||||
"""Attempt to establish a connection via FTP."""
|
||||
async def _get_latest_video_url(self):
|
||||
"""Retrieve the latest video file from the customized Yi FTP server."""
|
||||
from aioftp import Client, StatusCodeError
|
||||
|
||||
ftp = Client()
|
||||
ftp = Client(loop=self.hass.loop)
|
||||
try:
|
||||
await ftp.connect(self.host)
|
||||
await ftp.login(self.user, self.passwd)
|
||||
self._ftp = ftp
|
||||
except StatusCodeError as err:
|
||||
raise PlatformNotReady(err)
|
||||
|
||||
async def _get_latest_video_url(self):
|
||||
"""Retrieve the latest video file from the customized Yi FTP server."""
|
||||
from aioftp import StatusCodeError
|
||||
|
||||
try:
|
||||
await self._ftp.change_directory(self.path)
|
||||
await ftp.change_directory(self.path)
|
||||
dirs = []
|
||||
for path, attrs in await self._ftp.list():
|
||||
for path, attrs in await ftp.list():
|
||||
if attrs['type'] == 'dir' and '.' not in str(path):
|
||||
dirs.append(path)
|
||||
latest_dir = dirs[-1]
|
||||
await self._ftp.change_directory(latest_dir)
|
||||
await ftp.change_directory(latest_dir)
|
||||
|
||||
videos = []
|
||||
for path, _ in await self._ftp.list():
|
||||
for path, _ in await ftp.list():
|
||||
videos.append(path)
|
||||
if not videos:
|
||||
_LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
|
||||
return None
|
||||
|
||||
await ftp.quit()
|
||||
|
||||
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
|
||||
self.user, self.passwd, self.host, self.port, self.path,
|
||||
latest_dir, videos[-1])
|
||||
|
||||
15
homeassistant/components/cast/.translations/ca.json
Normal file
15
homeassistant/components/cast/.translations/ca.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.",
|
||||
"single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de Google Cast."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Voleu configurar Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/cs.json
Normal file
15
homeassistant/components/cast/.translations/cs.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "V s\u00edti nebyly nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed Google Cast.",
|
||||
"single_instance_allowed": "Pouze jedin\u00e1 konfigurace Google Cast je nezbytn\u00e1."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Chcete nastavit Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
14
homeassistant/components/cast/.translations/de.json
Normal file
14
homeassistant/components/cast/.translations/de.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "M\u00f6chten Sie Google Cast einrichten?",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
14
homeassistant/components/cast/.translations/hu.json
Normal file
14
homeassistant/components/cast/.translations/hu.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/it.json
Normal file
15
homeassistant/components/cast/.translations/it.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Nessun dispositivo Google Cast trovato in rete.",
|
||||
"single_instance_allowed": "\u00c8 necessaria una sola configurazione di Google Cast."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Vuoi configurare Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/ko.json
Normal file
15
homeassistant/components/cast/.translations/ko.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Googgle Cast \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
|
||||
"single_instance_allowed": "Google Cast\uc758 \ub2e8\uc77c \uad6c\uc131 \ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Google Cast\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/lb.json
Normal file
15
homeassistant/components/cast/.translations/lb.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Keng Google Cast Apparater am Netzwierk fonnt.",
|
||||
"single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun Google Cast ass n\u00e9ideg."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Soll Google Cast konfigur\u00e9iert ginn?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/nl.json
Normal file
15
homeassistant/components/cast/.translations/nl.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Geen Google Cast-apparaten gevonden op het netwerk.",
|
||||
"single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Google Cast nodig."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Wilt u Google Cast instellen?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/no.json
Normal file
15
homeassistant/components/cast/.translations/no.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Ingen Google Cast enheter funnet p\u00e5 nettverket.",
|
||||
"single_instance_allowed": "Kun en enkelt konfigurasjon av Google Cast er n\u00f8dvendig."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u00d8nsker du \u00e5 sette opp Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/pl.json
Normal file
15
homeassistant/components/cast/.translations/pl.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 Google Cast.",
|
||||
"single_instance_allowed": "Wymagana jest tylko jedna konfiguracja Google Cast."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Czy chcesz skonfigurowa\u0107 Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/ru.json
Normal file
15
homeassistant/components/cast/.translations/ru.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
|
||||
"single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Google Cast."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/sl.json
Normal file
15
homeassistant/components/cast/.translations/sl.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "V omre\u017eju niso najdene naprave Google Cast.",
|
||||
"single_instance_allowed": "Potrebna je samo ena konfiguracija Google Cast-a."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Ali \u017eelite nastaviti Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/sv.json
Normal file
15
homeassistant/components/cast/.translations/sv.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Inga Google Cast-enheter hittades i n\u00e4tverket.",
|
||||
"single_instance_allowed": "Endast en enda konfiguration av Google Cast \u00e4r n\u00f6dv\u00e4ndig."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Vill du konfigurera Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/vi.json
Normal file
15
homeassistant/components/cast/.translations/vi.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "Kh\u00f4ng t\u00ecm th\u1ea5y thi\u1ebft b\u1ecb Google Cast n\u00e0o tr\u00ean m\u1ea1ng.",
|
||||
"single_instance_allowed": "Ch\u1ec9 c\u1ea7n m\u1ed9t c\u1ea5u h\u00ecnh duy nh\u1ea5t c\u1ee7a Google Cast l\u00e0 \u0111\u1ee7."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Google Cast kh\u00f4ng?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/zh-Hans.json
Normal file
15
homeassistant/components/cast/.translations/zh-Hans.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 Google Cast \u8bbe\u5907\u3002",
|
||||
"single_instance_allowed": "\u53ea\u6709\u4e00\u6b21 Google Cast \u914d\u7f6e\u662f\u5fc5\u8981\u7684\u3002"
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
15
homeassistant/components/cast/.translations/zh-Hant.json
Normal file
15
homeassistant/components/cast/.translations/zh-Hant.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u8a2d\u5099\u3002",
|
||||
"single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002"
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u662f\u5426\u8981\u8a2d\u5b9a Google Cast\uff1f",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
"title": "Google Cast"
|
||||
}
|
||||
}
|
||||
@@ -470,7 +470,6 @@ async def async_unload_entry(hass, entry):
|
||||
class ClimateDevice(Entity):
|
||||
"""Representation of a climate device."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current state."""
|
||||
|
||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
# pylint: disable=import-error, no-name-in-module
|
||||
# pylint: disable=import-error
|
||||
class EQ3BTSmartThermostat(ClimateDevice):
|
||||
"""Representation of an eQ-3 Bluetooth Smart thermostat."""
|
||||
|
||||
|
||||
@@ -263,7 +263,6 @@ class GenericThermostat(ClimateDevice):
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
# pylint: disable=no-member
|
||||
if self._min_temp:
|
||||
return self._min_temp
|
||||
|
||||
@@ -273,7 +272,6 @@ class GenericThermostat(ClimateDevice):
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
# pylint: disable=no-member
|
||||
if self._max_temp:
|
||||
return self._max_temp
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the heatmiser thermostat."""
|
||||
from heatmiserV3 import heatmiser, connection
|
||||
|
||||
130
homeassistant/components/climate/homekit_controller.py
Normal file
130
homeassistant/components/climate/homekit_controller.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Support for Homekit climate devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.homekit_controller/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.homekit_controller import (
|
||||
HomeKitEntity, KNOWN_ACCESSORIES)
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import TEMP_CELSIUS, STATE_OFF, ATTR_TEMPERATURE
|
||||
|
||||
DEPENDENCIES = ['homekit_controller']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Map of Homekit operation modes to hass modes
|
||||
MODE_HOMEKIT_TO_HASS = {
|
||||
0: STATE_OFF,
|
||||
1: STATE_HEAT,
|
||||
2: STATE_COOL,
|
||||
}
|
||||
|
||||
# Map of hass operation modes to homekit modes
|
||||
MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Homekit climate."""
|
||||
if discovery_info is not None:
|
||||
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
|
||||
add_devices([HomeKitClimateDevice(accessory, discovery_info)], True)
|
||||
|
||||
|
||||
class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
||||
"""Representation of a Homekit climate device."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the device."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
self._current_mode = None
|
||||
self._valid_modes = []
|
||||
self._current_temp = None
|
||||
self._target_temp = None
|
||||
|
||||
def update_characteristics(self, characteristics):
|
||||
"""Synchronise device state with Home Assistant."""
|
||||
# pylint: disable=import-error
|
||||
from homekit import CharacteristicsTypes as ctypes
|
||||
|
||||
for characteristic in characteristics:
|
||||
ctype = characteristic['type']
|
||||
if ctype == ctypes.HEATING_COOLING_CURRENT:
|
||||
self._state = MODE_HOMEKIT_TO_HASS.get(
|
||||
characteristic['value'])
|
||||
if ctype == ctypes.HEATING_COOLING_TARGET:
|
||||
self._chars['target_mode'] = characteristic['iid']
|
||||
self._features |= SUPPORT_OPERATION_MODE
|
||||
self._current_mode = MODE_HOMEKIT_TO_HASS.get(
|
||||
characteristic['value'])
|
||||
self._valid_modes = [MODE_HOMEKIT_TO_HASS.get(
|
||||
mode) for mode in characteristic['valid-values']]
|
||||
elif ctype == ctypes.TEMPERATURE_CURRENT:
|
||||
self._current_temp = characteristic['value']
|
||||
elif ctype == ctypes.TEMPERATURE_TARGET:
|
||||
self._chars['target_temp'] = characteristic['iid']
|
||||
self._features |= SUPPORT_TARGET_TEMPERATURE
|
||||
self._target_temp = characteristic['value']
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['target_temp'],
|
||||
'value': temp}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['target_mode'],
|
||||
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current state."""
|
||||
# If the device reports its operating mode as off, it sometimes doesn't
|
||||
# report a new state.
|
||||
if self._current_mode == STATE_OFF:
|
||||
return STATE_OFF
|
||||
|
||||
if self._state == STATE_OFF and self._current_mode != STATE_OFF:
|
||||
return STATE_IDLE
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temp
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temp
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_mode
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._valid_modes
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
@@ -129,6 +129,9 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the MQTT climate devices."""
|
||||
if discovery_info is not None:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
|
||||
template_keys = (
|
||||
CONF_POWER_STATE_TEMPLATE,
|
||||
CONF_MODE_STATE_TEMPLATE,
|
||||
@@ -635,11 +638,9 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
# pylint: disable=no-member
|
||||
return self._min_temp
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
# pylint: disable=no-member
|
||||
return self._max_temp
|
||||
|
||||
@@ -26,9 +26,8 @@ DICT_MYS_TO_HA = {
|
||||
'Off': STATE_OFF,
|
||||
}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE |
|
||||
SUPPORT_OPERATION_MODE)
|
||||
FAN_LIST = ['Auto', 'Min', 'Normal', 'Max']
|
||||
OPERATION_LIST = [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
@@ -39,13 +38,24 @@ async def async_setup_platform(
|
||||
async_add_devices=async_add_devices)
|
||||
|
||||
|
||||
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
||||
"""Representation of a MySensors HVAC."""
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
features = SUPPORT_OPERATION_MODE
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_HVAC_SPEED in self._values:
|
||||
features = features | SUPPORT_FAN_MODE
|
||||
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
|
||||
set_req.V_HVAC_SETPOINT_HEAT in self._values):
|
||||
features = (
|
||||
features | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
else:
|
||||
features = features | SUPPORT_TARGET_TEMPERATURE
|
||||
return features
|
||||
|
||||
@property
|
||||
def assumed_state(self):
|
||||
@@ -103,7 +113,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
return [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
|
||||
return OPERATION_LIST
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
@@ -113,7 +123,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""List of available fan modes."""
|
||||
return ['Auto', 'Min', 'Normal', 'Max']
|
||||
return FAN_LIST
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
||||
@@ -5,15 +5,15 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.zwave/
|
||||
"""
|
||||
# Because we do not compile openzwave on CI
|
||||
# pylint: disable=import-error
|
||||
import logging
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
||||
DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
|
||||
from homeassistant.components.zwave import ZWaveDeviceEntity
|
||||
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
||||
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,6 +32,15 @@ DEVICE_MAPPINGS = {
|
||||
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
||||
}
|
||||
|
||||
STATE_MAPPINGS = {
|
||||
'Off': STATE_OFF,
|
||||
'Heat': STATE_HEAT,
|
||||
'Heat Mode': STATE_HEAT,
|
||||
'Heat (Default)': STATE_HEAT,
|
||||
'Cool': STATE_COOL,
|
||||
'Auto': STATE_AUTO,
|
||||
}
|
||||
|
||||
|
||||
def get_device(hass, values, **kwargs):
|
||||
"""Create Z-Wave entity device."""
|
||||
@@ -49,6 +58,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
self._current_temperature = None
|
||||
self._current_operation = None
|
||||
self._operation_list = None
|
||||
self._operation_mapping = None
|
||||
self._operating_state = None
|
||||
self._current_fan_mode = None
|
||||
self._fan_list = None
|
||||
@@ -87,10 +97,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
"""Handle the data changes for node values."""
|
||||
# Operation Mode
|
||||
if self.values.mode:
|
||||
self._current_operation = self.values.mode.data
|
||||
self._operation_list = []
|
||||
self._operation_mapping = {}
|
||||
operation_list = self.values.mode.data_items
|
||||
if operation_list:
|
||||
self._operation_list = list(operation_list)
|
||||
for mode in operation_list:
|
||||
ha_mode = STATE_MAPPINGS.get(mode)
|
||||
if ha_mode and ha_mode not in self._operation_mapping:
|
||||
self._operation_mapping[ha_mode] = mode
|
||||
self._operation_list.append(ha_mode)
|
||||
continue
|
||||
self._operation_list.append(mode)
|
||||
current_mode = self.values.mode.data
|
||||
self._current_operation = next(
|
||||
(key for key, value in self._operation_mapping.items()
|
||||
if value == current_mode), current_mode)
|
||||
_LOGGER.debug("self._operation_list=%s", self._operation_list)
|
||||
_LOGGER.debug("self._current_operation=%s", self._current_operation)
|
||||
|
||||
@@ -206,7 +227,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
if self.values.mode:
|
||||
self.values.mode.data = operation_mode
|
||||
self.values.mode.data = self._operation_mapping.get(
|
||||
operation_mode, operation_mode)
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing mode."""
|
||||
|
||||
@@ -198,7 +198,6 @@ async def async_setup(hass, config):
|
||||
class CoverDevice(Entity):
|
||||
"""Representation a cover."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return current position of cover.
|
||||
|
||||
@@ -24,7 +24,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class DemoCover(CoverDevice):
|
||||
"""Representation of a demo cover."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def __init__(self, hass, name, position=None, tilt_position=None,
|
||||
device_class=None, supported_features=None):
|
||||
"""Initialize the cover."""
|
||||
|
||||
@@ -73,7 +73,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class GaradgetCover(CoverDevice):
|
||||
"""Representation of a Garadget cover."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def __init__(self, hass, args):
|
||||
"""Initialize the cover."""
|
||||
self.particle_url = 'https://api.particle.io'
|
||||
|
||||
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.isy994/
|
||||
"""
|
||||
import logging
|
||||
from typing import Callable # noqa
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.cover import CoverDevice, DOMAIN
|
||||
from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS,
|
||||
|
||||
@@ -17,7 +17,7 @@ async def async_setup_platform(
|
||||
async_add_devices=async_add_devices)
|
||||
|
||||
|
||||
class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
||||
class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice):
|
||||
"""Representation of the value of a MySensors Cover child node."""
|
||||
|
||||
@property
|
||||
|
||||
@@ -72,7 +72,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class OpenGarageCover(CoverDevice):
|
||||
"""Representation of a OpenGarage cover."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def __init__(self, hass, args):
|
||||
"""Initialize the cover."""
|
||||
self.opengarage_url = 'http://{}:{}'.format(
|
||||
|
||||
@@ -21,6 +21,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
_id = shade.object_id() + shade.name()
|
||||
if _id not in hass.data[DOMAIN]['unique_ids']:
|
||||
add_devices([WinkCoverDevice(shade, hass)])
|
||||
for shade in pywink.get_shade_groups():
|
||||
_id = shade.object_id() + shade.name()
|
||||
if _id not in hass.data[DOMAIN]['unique_ids']:
|
||||
add_devices([WinkCoverDevice(shade, hass)])
|
||||
for door in pywink.get_garage_doors():
|
||||
_id = door.object_id() + door.name()
|
||||
if _id not in hass.data[DOMAIN]['unique_ids']:
|
||||
|
||||
@@ -42,7 +42,6 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
|
||||
def __init__(self, hass, values, invert_buttons):
|
||||
"""Initialize the Z-Wave rollershutter."""
|
||||
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
|
||||
# pylint: disable=no-member
|
||||
self._network = hass.data[zwave.const.DATA_NETWORK]
|
||||
self._open_id = None
|
||||
self._close_id = None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u041c\u043e\u0441\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d",
|
||||
"no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ",
|
||||
"one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u043e \u043a\u043e\u043f\u0438\u0435 \u043d\u0430 deCONZ"
|
||||
},
|
||||
|
||||
33
homeassistant/components/deconz/.translations/ca.json
Normal file
33
homeassistant/components/deconz/.translations/ca.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "L'enlla\u00e7 ja est\u00e0 configurat",
|
||||
"no_bridges": "No s'han descobert enlla\u00e7os amb deCONZ",
|
||||
"one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "No s'ha pogut obtenir una clau API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Amfitri\u00f3",
|
||||
"port": "Port (predeterminat: '80')"
|
||||
},
|
||||
"title": "Definiu la passarel\u00b7la deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ\n2. Prem el bot\u00f3 \"Desbloquejar passarel\u00b7la\"",
|
||||
"title": "Vincular amb deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Permet la importaci\u00f3 de sensors virtuals",
|
||||
"allow_deconz_groups": "Permet la importaci\u00f3 de grups deCONZ"
|
||||
},
|
||||
"title": "Opcions de configuraci\u00f3 addicionals per deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
}
|
||||
}
|
||||
33
homeassistant/components/deconz/.translations/cs.json
Normal file
33
homeassistant/components/deconz/.translations/cs.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "P\u0159emost\u011bn\u00ed je ji\u017e nakonfigurov\u00e1no",
|
||||
"no_bridges": "\u017d\u00e1dn\u00e9 deCONZ p\u0159emost\u011bn\u00ed nebyly nalezeny",
|
||||
"one_instance_only": "Komponent podporuje pouze jednu instanci deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "Nelze z\u00edskat kl\u00ed\u010d API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Hostitel",
|
||||
"port": "Port (v\u00fdchoz\u00ed hodnota: '80')"
|
||||
},
|
||||
"title": "Definujte br\u00e1nu deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "Odemkn\u011bte br\u00e1nu deCONZ, pro registraci v Home Assistant. \n\n 1. P\u0159ejd\u011bte do nastaven\u00ed syst\u00e9mu deCONZ \n 2. Stiskn\u011bte tla\u010d\u00edtko \"Unlock Gateway\"",
|
||||
"title": "Propojit s deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Povolit import virtu\u00e1ln\u00edch \u010didel",
|
||||
"allow_deconz_groups": "Povolit import skupin deCONZ "
|
||||
},
|
||||
"title": "Dal\u0161\u00ed mo\u017enosti konfigurace pro deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "Br\u00e1na deCONZ Zigbee"
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,14 @@
|
||||
"link": {
|
||||
"description": "Entsperren Sie Ihr deCONZ-Gateway, um sich bei Home Assistant zu registrieren. \n\n 1. Gehen Sie zu den deCONZ-Systemeinstellungen \n 2. Dr\u00fccken Sie die Taste \"Gateway entsperren\"",
|
||||
"title": "Mit deCONZ verbinden"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Import virtueller Sensoren zulassen",
|
||||
"allow_deconz_groups": "Import von deCONZ-Gruppen zulassen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee Gateway"
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,11 @@
|
||||
"title": "Link with deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"title": "Extra configuration options for deCONZ",
|
||||
"data": {
|
||||
"allow_clip_sensor": "Allow importing virtual sensors",
|
||||
"allow_deconz_groups": "Allow importing deCONZ groups"
|
||||
}
|
||||
},
|
||||
"title": "Extra configuration options for deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ Zigbee gateway"
|
||||
|
||||
32
homeassistant/components/deconz/.translations/fr.json
Normal file
32
homeassistant/components/deconz/.translations/fr.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ce pont est d\u00e9j\u00e0 configur\u00e9",
|
||||
"no_bridges": "Aucun pont deCONZ n'a \u00e9t\u00e9 d\u00e9couvert",
|
||||
"one_instance_only": "Le composant prend uniquement en charge une instance deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "Impossible d'obtenir une cl\u00e9 d'API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "H\u00f4te",
|
||||
"port": "Port (valeur par d\u00e9faut : 80)"
|
||||
},
|
||||
"title": "Initialiser la passerelle deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "D\u00e9verrouillez votre passerelle deCONZ pour vous enregistrer aupr\u00e8s de Home Assistant. \n\n 1. Acc\u00e9dez aux param\u00e8tres du syst\u00e8me deCONZ \n 2. Cliquez sur \"D\u00e9verrouiller la passerelle\"",
|
||||
"title": "Lien vers deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels"
|
||||
},
|
||||
"title": "Options de configuration suppl\u00e9mentaires pour deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "Passerelle deCONZ Zigbee"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "A bridge m\u00e1r konfigur\u00e1lva van",
|
||||
"no_bridges": "Nem tal\u00e1ltam deCONZ bridget",
|
||||
"one_instance_only": "Ez a komponens csak egy deCONZ egys\u00e9get t\u00e1mogat"
|
||||
},
|
||||
"error": {
|
||||
@@ -11,9 +13,11 @@
|
||||
"data": {
|
||||
"host": "H\u00e1zigazda (Host)",
|
||||
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa"
|
||||
},
|
||||
"link": {
|
||||
"description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot",
|
||||
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
|
||||
}
|
||||
},
|
||||
|
||||
26
homeassistant/components/deconz/.translations/it.json
Normal file
26
homeassistant/components/deconz/.translations/it.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Il Bridge \u00e8 gi\u00e0 configurato",
|
||||
"no_bridges": "Nessun bridge deCONZ rilevato",
|
||||
"one_instance_only": "Il componente supporto solo un'istanza di deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "Impossibile ottenere una API key"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Porta (valore di default: '80')"
|
||||
},
|
||||
"title": "Definisci il gateway deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "Sblocca il tuo gateway deCONZ per registrarlo in Home Assistant.\n\n1. Vai nelle impostazioni di sistema di deCONZ\n2. Premi il bottone \"Unlock Gateway\"",
|
||||
"title": "Collega con deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,16 @@
|
||||
},
|
||||
"link": {
|
||||
"description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ",
|
||||
"title": "deCONZ \uc640 \uc5f0\uacb0"
|
||||
"title": "deCONZ\uc640 \uc5f0\uacb0"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "\uac00\uc0c1 \uc13c\uc11c \uac00\uc838\uc624\uae30 \ud5c8\uc6a9",
|
||||
"allow_deconz_groups": "deCONZ \ub0b4\uc6a9 \uac00\uc838\uc624\uae30 \ud5c8\uc6a9"
|
||||
},
|
||||
"title": "deCONZ\ub97c \uc704\ud55c \ucd94\uac00 \uad6c\uc131 \uc635\uc158"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774"
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op\u00a0deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen",
|
||||
"title": "Link mat deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Erlaabt den Import vun virtuellen Sensoren",
|
||||
"allow_deconz_groups": "Erlaabt den Import vun deCONZ Gruppen"
|
||||
},
|
||||
"title": "Extra Konfiguratiouns Optiounen fir deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "Ontgrendel je deCONZ gateway om te registreren met Home Assistant.\n\n1. Ga naar deCONZ systeeminstellingen\n2. Druk op de knop \"Gateway ontgrendelen\"",
|
||||
"title": "Koppel met deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Sta het importeren van virtuele sensoren toe",
|
||||
"allow_deconz_groups": "Sta de import van deCONZ-groepen toe"
|
||||
},
|
||||
"title": "Extra configuratieopties voor deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger \n 2. Trykk p\u00e5 \"L\u00e5s opp gateway\" knappen",
|
||||
"title": "Koble til deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Tillat import av virtuelle sensorer",
|
||||
"allow_deconz_groups": "Tillat import av deCONZ grupper"
|
||||
},
|
||||
"title": "Ekstra konfigurasjonsalternativer for deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
"link": {
|
||||
"description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawie\u0144 systemu deCONZ \n 2. Naci\u015bnij przycisk \"Odblokuj bramk\u0119\"",
|
||||
"title": "Po\u0142\u0105cz z deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Zezwalaj na importowanie wirtualnych sensor\u00f3w"
|
||||
},
|
||||
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
32
homeassistant/components/deconz/.translations/pt-BR.json
Normal file
32
homeassistant/components/deconz/.translations/pt-BR.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "A ponte j\u00e1 est\u00e1 configurada",
|
||||
"no_bridges": "N\u00e3o h\u00e1 pontes de deCONZ descobertas",
|
||||
"one_instance_only": "Componente suporta apenas uma inst\u00e2ncia deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Hospedeiro",
|
||||
"port": "Porta (valor padr\u00e3o: '80')"
|
||||
},
|
||||
"title": "Defina o gateway deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
|
||||
"title": "Linkar com deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais"
|
||||
},
|
||||
"title": "Op\u00e7\u00f5es extras de configura\u00e7\u00e3o para deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "Gateway deCONZ Zigbee"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,32 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Bridge j\u00e1 est\u00e1 configurada"
|
||||
}
|
||||
"already_configured": "Bridge j\u00e1 est\u00e1 configurada",
|
||||
"no_bridges": "Nenhum deCONZ descoberto",
|
||||
"one_instance_only": "Componente suporta apenas uma conex\u00e3o deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Servidor",
|
||||
"port": "Porta (por omiss\u00e3o: '80')"
|
||||
},
|
||||
"title": "Defina o gateway deCONZ"
|
||||
},
|
||||
"link": {
|
||||
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
|
||||
"title": "Link com deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais"
|
||||
},
|
||||
"title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb",
|
||||
"title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432",
|
||||
"allow_deconz_groups": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0433\u0440\u0443\u043f\u043f deCONZ"
|
||||
},
|
||||
"title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"",
|
||||
"title": "Povezava z deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Dovoli uvoz virtualnih senzorjev",
|
||||
"allow_deconz_groups": "Dovoli uvoz deCONZ skupin"
|
||||
},
|
||||
"title": "Dodatne mo\u017enosti konfiguracije za deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
33
homeassistant/components/deconz/.translations/sv.json
Normal file
33
homeassistant/components/deconz/.translations/sv.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Bryggan \u00e4r redan konfigurerad",
|
||||
"no_bridges": "Inga deCONZ-bryggor uppt\u00e4cktes",
|
||||
"one_instance_only": "Komponenten st\u00f6djer endast en deCONZ-instans"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "Det gick inte att ta emot en API-nyckel"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "V\u00e4rd",
|
||||
"port": "Port (standardv\u00e4rde: '80')"
|
||||
},
|
||||
"title": "Definiera deCONZ-gatewaye"
|
||||
},
|
||||
"link": {
|
||||
"description": "L\u00e5s upp din deCONZ-gateway f\u00f6r att registrera dig med Home Assistant. \n\n 1. G\u00e5 till deCONZ-systeminst\u00e4llningarna \n 2. Tryck p\u00e5 \"L\u00e5s upp gateway\"-knappen",
|
||||
"title": "L\u00e4nka med deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Till\u00e5t import av virtuella sensorer",
|
||||
"allow_deconz_groups": "Till\u00e5t import av deCONZ-grupper"
|
||||
},
|
||||
"title": "Extra konfigurationsalternativ f\u00f6r deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
}
|
||||
}
|
||||
26
homeassistant/components/deconz/.translations/vi.json
Normal file
26
homeassistant/components/deconz/.translations/vi.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "C\u1ea7u \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1ea5u h\u00ecnh",
|
||||
"no_bridges": "Kh\u00f4ng t\u00ecm th\u1ea5y c\u1ea7u deCONZ n\u00e0o",
|
||||
"one_instance_only": "Th\u00e0nh ph\u1ea7n ch\u1ec9 h\u1ed7 tr\u1ee3 m\u1ed9t c\u00e1 th\u1ec3 deCONZ"
|
||||
},
|
||||
"error": {
|
||||
"no_key": "Kh\u00f4ng th\u1ec3 l\u1ea5y kh\u00f3a API"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"port": "C\u1ed5ng (gi\u00e1 tr\u1ecb m\u1eb7c \u0111\u1ecbnh: '80')"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "Cho ph\u00e9p nh\u1eadp c\u1ea3m bi\u1ebfn \u1ea3o",
|
||||
"allow_deconz_groups": "Cho ph\u00e9p nh\u1eadp c\u00e1c nh\u00f3m deCONZ"
|
||||
},
|
||||
"title": "T\u00f9y ch\u1ecdn c\u1ea5u h\u00ecnh b\u1ed5 sung cho deCONZ"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "\u89e3\u9501\u60a8\u7684 deCONZ \u7f51\u5173\u4ee5\u6ce8\u518c\u5230 Home Assistant\u3002 \n\n 1. \u524d\u5f80 deCONZ \u7cfb\u7edf\u8bbe\u7f6e\n 2. \u70b9\u51fb\u201c\u89e3\u9501\u7f51\u5173\u201d\u6309\u94ae",
|
||||
"title": "\u8fde\u63a5 deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "\u5141\u8bb8\u5bfc\u5165\u865a\u62df\u4f20\u611f\u5668",
|
||||
"allow_deconz_groups": "\u5141\u8bb8\u5bfc\u5165 deCONZ \u7fa4\u7ec4"
|
||||
},
|
||||
"title": "deCONZ \u7684\u9644\u52a0\u914d\u7f6e\u9879"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
|
||||
"no_bridges": "\u672a\u641c\u5c0b\u5230 deCONZ Bridfe",
|
||||
"one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u5be6\u4f8b"
|
||||
},
|
||||
@@ -18,6 +19,13 @@
|
||||
"link": {
|
||||
"description": "\u89e3\u9664 deCONZ \u7db2\u95dc\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u89e3\u9664\u7db2\u95dc\u9396\u5b9a\uff08Unlock Gateway\uff09\u300d\u6309\u9215",
|
||||
"title": "\u9023\u7d50\u81f3 deCONZ"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"allow_clip_sensor": "\u5141\u8a31\u532f\u5165\u865b\u64ec\u611f\u61c9\u5668",
|
||||
"allow_deconz_groups": "\u5141\u8a31\u532f\u5165 deCONZ \u7fa4\u7d44"
|
||||
},
|
||||
"title": "deCONZ \u9644\u52a0\u8a2d\u5b9a\u9078\u9805"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
|
||||
@@ -22,7 +22,7 @@ from .const import (
|
||||
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
|
||||
|
||||
REQUIREMENTS = ['pydeconz==38']
|
||||
REQUIREMENTS = ['pydeconz==39']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
|
||||
@@ -163,9 +163,6 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
|
||||
if CONF_API_KEY not in import_config:
|
||||
return await self.async_step_link()
|
||||
|
||||
self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = True
|
||||
self.deconz_config[CONF_ALLOW_DECONZ_GROUPS] = True
|
||||
return self.async_create_entry(
|
||||
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
|
||||
data=self.deconz_config
|
||||
)
|
||||
user_input = {CONF_ALLOW_CLIP_SENSOR: True,
|
||||
CONF_ALLOW_DECONZ_GROUPS: True}
|
||||
return await self.async_step_options(user_input=user_input)
|
||||
|
||||
@@ -50,7 +50,6 @@ class CiscoDeviceScanner(DeviceScanner):
|
||||
self.success_init = self._update_info()
|
||||
_LOGGER.info('cisco_ios scanner initialized')
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get the firmware doesn't save the name of the wireless device."""
|
||||
return None
|
||||
|
||||
@@ -22,7 +22,7 @@ from homeassistant.components.device_tracker import (
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PORT)
|
||||
|
||||
REQUIREMENTS = ['aiofreepybox==0.0.3']
|
||||
REQUIREMENTS = ['aiofreepybox==0.0.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ https://home-assistant.io/components/device_tracker.gpslogger/
|
||||
import logging
|
||||
from hmac import compare_digest
|
||||
|
||||
from aiohttp.web import Request, HTTPUnauthorized # NOQA
|
||||
from aiohttp.web import Request, HTTPUnauthorized
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@@ -61,7 +61,6 @@ class LinksysAPDeviceScanner(DeviceScanner):
|
||||
|
||||
return self.last_results
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""
|
||||
Return the name (if known) of the device.
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
|
||||
|
||||
REQUIREMENTS = ['librouteros==1.0.5']
|
||||
REQUIREMENTS = ['librouteros==2.1.0']
|
||||
|
||||
MTK_DEFAULT_API_PORT = '8728'
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
id(device.gateway), device.node_id, device.child_id,
|
||||
device.value_type)
|
||||
async_dispatcher_connect(
|
||||
hass, mysensors.SIGNAL_CALLBACK.format(*dev_id),
|
||||
hass, mysensors.const.SIGNAL_CALLBACK.format(*dev_id),
|
||||
device.async_update_callback)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MySensorsDeviceScanner(mysensors.MySensorsDevice):
|
||||
class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
||||
"""Represent a MySensors scanner."""
|
||||
|
||||
def __init__(self, async_see, *args):
|
||||
|
||||
@@ -74,8 +74,6 @@ class SnmpScanner(DeviceScanner):
|
||||
return [client['mac'] for client in self.last_results
|
||||
if client.get('mac')]
|
||||
|
||||
# Suppressing no-self-use warning
|
||||
# pylint: disable=R0201
|
||||
def get_device_name(self, device):
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
# We have no names
|
||||
@@ -106,7 +104,6 @@ class SnmpScanner(DeviceScanner):
|
||||
if errindication:
|
||||
_LOGGER.error("SNMPLIB error: %s", errindication)
|
||||
return
|
||||
# pylint: disable=no-member
|
||||
if errstatus:
|
||||
_LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(),
|
||||
errindex and restable[int(errindex) - 1][0] or '?')
|
||||
|
||||
@@ -68,7 +68,6 @@ class TplinkDeviceScanner(DeviceScanner):
|
||||
self._update_info()
|
||||
return self.last_results
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get firmware doesn't save the name of the wireless device."""
|
||||
return None
|
||||
@@ -103,7 +102,6 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
|
||||
self._update_info()
|
||||
return self.last_results.keys()
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get firmware doesn't save the name of the wireless device."""
|
||||
return self.last_results.get(device)
|
||||
@@ -164,7 +162,6 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
|
||||
self._log_out()
|
||||
return self.last_results.keys()
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get the firmware doesn't save the name of the wireless device.
|
||||
|
||||
@@ -273,7 +270,6 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
|
||||
self._update_info()
|
||||
return self.last_results
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get the name of the wireless device."""
|
||||
return None
|
||||
@@ -349,7 +345,6 @@ class Tplink5DeviceScanner(TplinkDeviceScanner):
|
||||
self._update_info()
|
||||
return self.last_results.keys()
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""Get firmware doesn't save the name of the wireless device."""
|
||||
return None
|
||||
|
||||
@@ -27,6 +27,7 @@ ATTR_MEMORY = 'memory'
|
||||
ATTR_REGION = 'region'
|
||||
ATTR_VCPUS = 'vcpus'
|
||||
|
||||
CONF_ATTRIBUTION = 'Data provided by Digital Ocean'
|
||||
CONF_DROPLETS = 'droplets'
|
||||
|
||||
DATA_DIGITAL_OCEAN = 'data_do'
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.discovery import async_load_platform, async_discover
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['netdisco==1.4.1']
|
||||
REQUIREMENTS = ['netdisco==1.5.0']
|
||||
|
||||
DOMAIN = 'discovery'
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ def setup(hass, config):
|
||||
Will automatically load thermostat and sensor components to support
|
||||
devices discovered on the network.
|
||||
"""
|
||||
# pylint: disable=import-error
|
||||
global NETWORK
|
||||
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
|
||||
@@ -91,9 +91,11 @@ def setup(hass, yaml_config):
|
||||
server_port=config.listen_port,
|
||||
api_password=None,
|
||||
ssl_certificate=None,
|
||||
ssl_peer_certificate=None,
|
||||
ssl_key=None,
|
||||
cors_origins=None,
|
||||
use_x_forwarded_for=False,
|
||||
trusted_proxies=[],
|
||||
trusted_networks=[],
|
||||
login_threshold=0,
|
||||
is_ban_enabled=False
|
||||
|
||||
@@ -21,11 +21,12 @@ from homeassistant.components import websocket_api
|
||||
from homeassistant.config import find_config_file, load_yaml_config_file
|
||||
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.translation import async_get_translations
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180617.0']
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180708.0']
|
||||
|
||||
DOMAIN = 'frontend'
|
||||
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
|
||||
@@ -106,9 +107,9 @@ SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_TRANSLATIONS,
|
||||
vol.Required('language'): str,
|
||||
})
|
||||
WS_TYPE_GET_EXPERIMENTAL_UI = 'frontend/experimental_ui'
|
||||
SCHEMA_GET_EXPERIMENTAL_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_EXPERIMENTAL_UI,
|
||||
WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config'
|
||||
SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_LOVELACE_UI,
|
||||
})
|
||||
|
||||
|
||||
@@ -199,8 +200,8 @@ def add_manifest_json_key(key, val):
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the serving of the frontend."""
|
||||
if list(hass.auth.async_auth_providers):
|
||||
client = await hass.auth.async_create_client(
|
||||
if hass.auth.active:
|
||||
client = await hass.auth.async_get_or_create_client(
|
||||
'Home Assistant Frontend',
|
||||
redirect_uris=['/'],
|
||||
no_secret=True,
|
||||
@@ -216,8 +217,8 @@ async def async_setup(hass, config):
|
||||
WS_TYPE_GET_TRANSLATIONS, websocket_get_translations,
|
||||
SCHEMA_GET_TRANSLATIONS)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_GET_EXPERIMENTAL_UI, websocket_experimental_config,
|
||||
SCHEMA_GET_EXPERIMENTAL_UI)
|
||||
WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
|
||||
SCHEMA_GET_LOVELACE_UI)
|
||||
hass.http.register_view(ManifestJSONView)
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
@@ -265,7 +266,7 @@ async def async_setup(hass, config):
|
||||
await asyncio.wait(
|
||||
[async_register_built_in_panel(hass, panel) for panel in (
|
||||
'dev-event', 'dev-info', 'dev-service', 'dev-state',
|
||||
'dev-template', 'dev-mqtt', 'kiosk', 'experimental-ui')],
|
||||
'dev-template', 'dev-mqtt', 'kiosk', 'lovelace')],
|
||||
loop=hass.loop)
|
||||
|
||||
hass.data[DATA_FINALIZE_PANEL] = async_finalize_panel
|
||||
@@ -499,15 +500,26 @@ def websocket_get_translations(hass, connection, msg):
|
||||
hass.async_add_job(send_translations())
|
||||
|
||||
|
||||
def websocket_experimental_config(hass, connection, msg):
|
||||
"""Send experimental UI config over websocket config."""
|
||||
def websocket_lovelace_config(hass, connection, msg):
|
||||
"""Send lovelace UI config over websocket config."""
|
||||
async def send_exp_config():
|
||||
"""Send experimental frontend config."""
|
||||
config = await hass.async_add_job(
|
||||
load_yaml, hass.config.path('experimental-ui.yaml'))
|
||||
"""Send lovelace frontend config."""
|
||||
error = None
|
||||
try:
|
||||
config = await hass.async_add_job(
|
||||
load_yaml, hass.config.path('ui-lovelace.yaml'))
|
||||
message = websocket_api.result_message(
|
||||
msg['id'], config
|
||||
)
|
||||
except FileNotFoundError:
|
||||
error = ('file_not_found',
|
||||
'Could not find ui-lovelace.yaml in your config dir.')
|
||||
except HomeAssistantError as err:
|
||||
error = 'load_error', str(err)
|
||||
|
||||
connection.send_message_outside(websocket_api.result_message(
|
||||
msg['id'], config
|
||||
))
|
||||
if error is not None:
|
||||
message = websocket_api.error_message(msg['id'], *error)
|
||||
|
||||
connection.send_message_outside(message)
|
||||
|
||||
hass.async_add_job(send_exp_config())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user