mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
commit
df3e17a983
@ -69,6 +69,7 @@ omit =
|
||||
homeassistant/components/avea/light.py
|
||||
homeassistant/components/avion/light.py
|
||||
homeassistant/components/azure_event_hub/*
|
||||
homeassistant/components/azure_service_bus/*
|
||||
homeassistant/components/baidu/tts.py
|
||||
homeassistant/components/beewi_smartclim/sensor.py
|
||||
homeassistant/components/bbb_gpio/*
|
||||
@ -274,7 +275,6 @@ omit =
|
||||
homeassistant/components/growatt_server/sensor.py
|
||||
homeassistant/components/gstreamer/media_player.py
|
||||
homeassistant/components/gtfs/sensor.py
|
||||
homeassistant/components/gtt/sensor.py
|
||||
homeassistant/components/habitica/*
|
||||
homeassistant/components/hangouts/*
|
||||
homeassistant/components/hangouts/__init__.py
|
||||
@ -499,6 +499,7 @@ omit =
|
||||
homeassistant/components/panasonic_bluray/media_player.py
|
||||
homeassistant/components/panasonic_viera/media_player.py
|
||||
homeassistant/components/pandora/media_player.py
|
||||
homeassistant/components/pcal9535a/*
|
||||
homeassistant/components/pencom/switch.py
|
||||
homeassistant/components/philips_js/media_player.py
|
||||
homeassistant/components/pi_hole/sensor.py
|
||||
@ -720,6 +721,7 @@ omit =
|
||||
homeassistant/components/uber/sensor.py
|
||||
homeassistant/components/ubus/device_tracker.py
|
||||
homeassistant/components/ue_smart_radio/media_player.py
|
||||
homeassistant/components/unifiled/*
|
||||
homeassistant/components/upcloud/*
|
||||
homeassistant/components/upnp/*
|
||||
homeassistant/components/upc_connect/*
|
||||
|
42
.pre-commit-config-all.yaml
Normal file
42
.pre-commit-config-all.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
# This configuration includes the full set of hooks we use. In
|
||||
# addition to the defaults (see .pre-commit-config.yaml), this
|
||||
# includes hooks that require our development and test dependencies
|
||||
# installed and the virtualenv containing them active by the time
|
||||
# pre-commit runs to produce correct results.
|
||||
#
|
||||
# If this is not a problem for your workflow, using this config is
|
||||
# recommended, install it with
|
||||
# pre-commit install --config .pre-commit-config-all.yaml
|
||||
# Otherwise, see the default .pre-commit-config.yaml for a lighter one.
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.10b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.9
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==4.0.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
# Using a local "system" mypy instead of the mypy hook, because its
|
||||
# results depend on what is installed. And the mypy hook runs in a
|
||||
# virtualenv of its own, meaning we'd need to install and maintain
|
||||
# another set of our dependencies there... no. Use the "system" one
|
||||
# and reuse the environment that is set up anyway already instead.
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
files: ^homeassistant/.+\.py$
|
@ -1,6 +1,13 @@
|
||||
# This configuration includes the default, minimal set of hooks to be
|
||||
# run on all commits. It requires no specific setup and one can just
|
||||
# start using pre-commit with it.
|
||||
#
|
||||
# See .pre-commit-config-all.yaml for a more complete one that comes
|
||||
# with a better coverage at the cost of some specific setup needed.
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.3b0
|
||||
rev: 19.10b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@ -8,24 +15,10 @@ repos:
|
||||
- --quiet
|
||||
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.8
|
||||
rev: 3.7.9
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.3.1
|
||||
- pydocstyle==4.0.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==4.0.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
# Using a local "system" mypy instead of the mypy hook, because its
|
||||
# results depend on what is installed. And the mypy hook runs in a
|
||||
# virtualenv of its own, meaning we'd need to install and maintain
|
||||
# another set of our dependencies there... no. Use the "system" one
|
||||
# and reuse the environment that is set up anyway already instead.
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
files: ^homeassistant/.+\.py$
|
||||
|
@ -19,7 +19,7 @@ matrix:
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=lint
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=pylint PYLINT_ARGS=--jobs=0
|
||||
env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=typing
|
||||
- python: "3.6.1"
|
||||
@ -33,4 +33,4 @@ cache:
|
||||
- $HOME/.cache/pre-commit
|
||||
install: pip install -U tox
|
||||
language: python
|
||||
script: travis_wait 50 tox --develop
|
||||
script: ${TRAVIS_WAIT:+travis_wait $TRAVIS_WAIT} tox --develop
|
||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@ -33,7 +33,7 @@
|
||||
{
|
||||
"label": "Flake8",
|
||||
"type": "shell",
|
||||
"command": "flake8 homeassistant tests",
|
||||
"command": "pre-commit run flake8 --all-files",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
|
15
CODEOWNERS
15
CODEOWNERS
@ -19,6 +19,7 @@ homeassistant/components/airly/* @bieniu
|
||||
homeassistant/components/airvisual/* @bachya
|
||||
homeassistant/components/alarm_control_panel/* @colinodell
|
||||
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
|
||||
homeassistant/components/almond/* @gcampax @balloob
|
||||
homeassistant/components/alpha_vantage/* @fabaff
|
||||
homeassistant/components/amazon_polly/* @robbiet480
|
||||
homeassistant/components/ambiclimate/* @danielhiversen
|
||||
@ -42,6 +43,7 @@ homeassistant/components/awair/* @danielsjf
|
||||
homeassistant/components/aws/* @awarecan @robbiet480
|
||||
homeassistant/components/axis/* @kane610
|
||||
homeassistant/components/azure_event_hub/* @eavanvalkenburg
|
||||
homeassistant/components/azure_service_bus/* @hfurubotten
|
||||
homeassistant/components/beewi_smartclim/* @alemuro
|
||||
homeassistant/components/bitcoin/* @fabaff
|
||||
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
||||
@ -59,6 +61,7 @@ homeassistant/components/cisco_webex_teams/* @fbradyirl
|
||||
homeassistant/components/ciscospark/* @fbradyirl
|
||||
homeassistant/components/cloud/* @home-assistant/cloud
|
||||
homeassistant/components/cloudflare/* @ludeeus
|
||||
homeassistant/components/comfoconnect/* @michaelarnauts
|
||||
homeassistant/components/config/* @home-assistant/core
|
||||
homeassistant/components/configurator/* @home-assistant/core
|
||||
homeassistant/components/conversation/* @home-assistant/core
|
||||
@ -155,9 +158,11 @@ homeassistant/components/iqvia/* @bachya
|
||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||
homeassistant/components/izone/* @Swamp-Ig
|
||||
homeassistant/components/jewish_calendar/* @tsvi
|
||||
homeassistant/components/juicenet/* @jesserockz
|
||||
homeassistant/components/kaiterra/* @Michsior14
|
||||
homeassistant/components/keba/* @dannerph
|
||||
homeassistant/components/keenetic_ndms2/* @foxel
|
||||
homeassistant/components/keyboard_remote/* @bendavid
|
||||
homeassistant/components/knx/* @Julius2342
|
||||
homeassistant/components/kodi/* @armills
|
||||
homeassistant/components/konnected/* @heythisisnate
|
||||
@ -173,6 +178,7 @@ homeassistant/components/logi_circle/* @evanjd
|
||||
homeassistant/components/lovelace/* @home-assistant/frontend
|
||||
homeassistant/components/luci/* @fbradyirl @mzdrale
|
||||
homeassistant/components/luftdaten/* @fabaff
|
||||
homeassistant/components/lutron/* @JonGilmore
|
||||
homeassistant/components/mastodon/* @fabaff
|
||||
homeassistant/components/matrix/* @tinloaf
|
||||
homeassistant/components/mcp23017/* @jardiamj
|
||||
@ -221,13 +227,14 @@ homeassistant/components/oru/* @bvlaicu
|
||||
homeassistant/components/owlet/* @oblogic7
|
||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||
homeassistant/components/pcal9535a/* @Shulyaka
|
||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||
homeassistant/components/philips_js/* @elupus
|
||||
homeassistant/components/pi_hole/* @fabaff @johnluetke
|
||||
homeassistant/components/plaato/* @JohNan
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/plex/* @jjlawren
|
||||
homeassistant/components/plugwise/* @laetificat @CoMPaTech
|
||||
homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew
|
||||
homeassistant/components/point/* @fredrike
|
||||
homeassistant/components/ps4/* @ktnrg45
|
||||
homeassistant/components/ptvsd/* @swamp-ig
|
||||
@ -247,6 +254,7 @@ homeassistant/components/rfxtrx/* @danielhiversen
|
||||
homeassistant/components/rmvtransport/* @cgtobi
|
||||
homeassistant/components/roomba/* @pschmitt
|
||||
homeassistant/components/saj/* @fredericvl
|
||||
homeassistant/components/samsungtv/* @escoand
|
||||
homeassistant/components/scene/* @home-assistant/core
|
||||
homeassistant/components/scrape/* @fabaff
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
@ -277,6 +285,7 @@ homeassistant/components/sql/* @dgomes
|
||||
homeassistant/components/statistics/* @fabaff
|
||||
homeassistant/components/stiebel_eltron/* @fucm
|
||||
homeassistant/components/stream/* @hunterjm
|
||||
homeassistant/components/stt/* @pvizeli
|
||||
homeassistant/components/suez_water/* @ooii
|
||||
homeassistant/components/sun/* @Swamp-Ig
|
||||
homeassistant/components/supla/* @mwegrzynek
|
||||
@ -305,12 +314,13 @@ homeassistant/components/tplink/* @rytilahti
|
||||
homeassistant/components/traccar/* @ludeeus
|
||||
homeassistant/components/tradfri/* @ggravlingen
|
||||
homeassistant/components/trafikverket_train/* @endor-force
|
||||
homeassistant/components/transmission/* @engrbm87
|
||||
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
||||
homeassistant/components/tts/* @robbiet480
|
||||
homeassistant/components/twentemilieu/* @frenck
|
||||
homeassistant/components/twilio_call/* @robbiet480
|
||||
homeassistant/components/twilio_sms/* @robbiet480
|
||||
homeassistant/components/unifi/* @kane610
|
||||
homeassistant/components/unifiled/* @florisvdk
|
||||
homeassistant/components/upc_connect/* @pvizeli
|
||||
homeassistant/components/upcloud/* @scop
|
||||
homeassistant/components/updater/* @home-assistant/core
|
||||
@ -333,6 +343,7 @@ homeassistant/components/weblink/* @home-assistant/core
|
||||
homeassistant/components/websocket_api/* @home-assistant/core
|
||||
homeassistant/components/wemo/* @sqldiablo
|
||||
homeassistant/components/withings/* @vangorra
|
||||
homeassistant/components/wled/* @frenck
|
||||
homeassistant/components/worldclock/* @fabaff
|
||||
homeassistant/components/wwlln/* @bachya
|
||||
homeassistant/components/xbox_live/* @MartinHjelmare
|
||||
|
@ -24,9 +24,9 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
|
||||
WORKDIR /workspaces
|
||||
|
||||
# Install Python dependencies from requirements
|
||||
COPY requirements_test.txt homeassistant/package_constraints.txt ./
|
||||
COPY requirements_test.txt requirements_test_pre_commit.txt homeassistant/package_constraints.txt ./
|
||||
RUN pip3 install -r requirements_test.txt -c package_constraints.txt \
|
||||
&& rm -f requirements_test.txt package_constraints.txt
|
||||
&& rm -f requirements_test.txt package_constraints.txt requirements_test_pre_commit.txt
|
||||
|
||||
# Set the default shell to bash instead of sh
|
||||
ENV SHELL /bin/bash
|
||||
|
@ -45,7 +45,7 @@ stages:
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
pre-commit run flake8 --all-files
|
||||
@ -84,7 +84,7 @@ stages:
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
pre-commit run black --all-files
|
||||
@ -127,7 +127,7 @@ stages:
|
||||
set -e
|
||||
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 -n 2 --dist loadfile -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar tests
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container)'
|
||||
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
|
||||
@ -135,7 +135,7 @@ stages:
|
||||
set -e
|
||||
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 -n 2 --dist loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||
codecov --token $(codecovToken)
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container) / coverage'
|
||||
@ -182,8 +182,8 @@ stages:
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
pre-commit run mypy --all-files
|
||||
pre-commit run --config .pre-commit-config-all.yaml mypy --all-files
|
||||
displayName: 'Run mypy'
|
||||
|
@ -261,7 +261,7 @@ class AuthManager:
|
||||
"""Enable a multi-factor auth module for user."""
|
||||
if user.system_generated:
|
||||
raise ValueError(
|
||||
"System generated users cannot enable " "multi-factor auth module."
|
||||
"System generated users cannot enable multi-factor auth module."
|
||||
)
|
||||
|
||||
module = self.get_auth_mfa_module(mfa_module_id)
|
||||
@ -276,7 +276,7 @@ class AuthManager:
|
||||
"""Disable a multi-factor auth module for user."""
|
||||
if user.system_generated:
|
||||
raise ValueError(
|
||||
"System generated users cannot disable " "multi-factor auth module."
|
||||
"System generated users cannot disable multi-factor auth module."
|
||||
)
|
||||
|
||||
module = self.get_auth_mfa_module(mfa_module_id)
|
||||
@ -320,7 +320,7 @@ class AuthManager:
|
||||
|
||||
if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM):
|
||||
raise ValueError(
|
||||
"System generated users can only have system type " "refresh tokens"
|
||||
"System generated users can only have system type refresh tokens"
|
||||
)
|
||||
|
||||
if token_type == models.TOKEN_TYPE_NORMAL and client_id is None:
|
||||
@ -330,7 +330,7 @@ class AuthManager:
|
||||
token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
|
||||
and client_name is None
|
||||
):
|
||||
raise ValueError("Client_name is required for long-lived access " "token")
|
||||
raise ValueError("Client_name is required for long-lived access token")
|
||||
|
||||
if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN:
|
||||
for token in user.refresh_tokens.values():
|
||||
|
@ -215,7 +215,11 @@ class TotpSetupFlow(SetupFlow):
|
||||
|
||||
else:
|
||||
hass = self._auth_module.hass
|
||||
self._ota_secret, self._url, self._image = await hass.async_add_executor_job(
|
||||
(
|
||||
self._ota_secret,
|
||||
self._url,
|
||||
self._image,
|
||||
) = await hass.async_add_executor_job(
|
||||
_generate_secret_and_qr_code, # type: ignore
|
||||
str(self._user.name),
|
||||
)
|
||||
|
@ -33,6 +33,8 @@ STAGE_1_INTEGRATIONS = {
|
||||
"recorder",
|
||||
# To make sure we forward data to other instances
|
||||
"mqtt_eventstream",
|
||||
# To provide account link implementations
|
||||
"cloud",
|
||||
}
|
||||
|
||||
|
||||
|
22
homeassistant/components/abode/.translations/bg.json
Normal file
22
homeassistant/components/abode/.translations/bg.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Abode.",
|
||||
"identifier_exists": "\u041f\u0440\u043e\u0444\u0438\u043b\u044a\u0442 \u0435 \u0432\u0435\u0447\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d.",
|
||||
"invalid_credentials": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
|
||||
"username": "E-mail \u0430\u0434\u0440\u0435\u0441"
|
||||
},
|
||||
"title": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0412\u0430\u0448\u0430\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0437\u0430 \u0432\u0445\u043e\u0434 \u0432 Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/cs.json
Normal file
22
homeassistant/components/abode/.translations/cs.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Je povolena pouze jedna konfigurace Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Nelze se p\u0159ipojit k Abode.",
|
||||
"identifier_exists": "\u00da\u010det je ji\u017e zaregistrov\u00e1n.",
|
||||
"invalid_credentials": "Neplatn\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Heslo",
|
||||
"username": "E-mailov\u00e1 adresa"
|
||||
},
|
||||
"title": "Vypl\u0148te p\u0159ihla\u0161ovac\u00ed \u00fadaje Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
@ -1,8 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Somente uma \u00fanica configura\u00e7\u00e3o de Abode \u00e9 permitida."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "N\u00e3o foi poss\u00edvel conectar ao Abode.",
|
||||
"identifier_exists": "Conta j\u00e1 cadastrada.",
|
||||
"invalid_credentials": "Credenciais inv\u00e1lidas."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Senha",
|
||||
"username": "Endere\u00e7o de e-mail"
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,17 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import ATTRIBUTION, DOMAIN, DEFAULT_CACHEDB
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
DEFAULT_CACHEDB,
|
||||
SIGNAL_CAPTURE_IMAGE,
|
||||
SIGNAL_TRIGGER_QUICK_ACTION,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -89,7 +96,7 @@ class AbodeSystem:
|
||||
|
||||
self.abode = abode
|
||||
self.polling = polling
|
||||
self.devices = []
|
||||
self.entity_ids = set()
|
||||
self.logout_listener = None
|
||||
|
||||
|
||||
@ -179,27 +186,29 @@ def setup_hass_services(hass):
|
||||
"""Capture a new image."""
|
||||
entity_ids = call.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
target_devices = [
|
||||
device
|
||||
for device in hass.data[DOMAIN].devices
|
||||
if device.entity_id in entity_ids
|
||||
target_entities = [
|
||||
entity_id
|
||||
for entity_id in hass.data[DOMAIN].entity_ids
|
||||
if entity_id in entity_ids
|
||||
]
|
||||
|
||||
for device in target_devices:
|
||||
device.capture()
|
||||
for entity_id in target_entities:
|
||||
signal = SIGNAL_CAPTURE_IMAGE.format(entity_id)
|
||||
dispatcher_send(hass, signal)
|
||||
|
||||
def trigger_quick_action(call):
|
||||
"""Trigger a quick action."""
|
||||
entity_ids = call.data.get(ATTR_ENTITY_ID, None)
|
||||
|
||||
target_devices = [
|
||||
device
|
||||
for device in hass.data[DOMAIN].devices
|
||||
if device.entity_id in entity_ids
|
||||
target_entities = [
|
||||
entity_id
|
||||
for entity_id in hass.data[DOMAIN].entity_ids
|
||||
if entity_id in entity_ids
|
||||
]
|
||||
|
||||
for device in target_devices:
|
||||
device.trigger()
|
||||
for entity_id in target_entities:
|
||||
signal = SIGNAL_TRIGGER_QUICK_ACTION.format(entity_id)
|
||||
dispatcher_send(hass, signal)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA
|
||||
@ -290,6 +299,7 @@ class AbodeDevice(Entity):
|
||||
self._device.device_id,
|
||||
self._update_callback,
|
||||
)
|
||||
self.hass.data[DOMAIN].entity_ids.add(self.entity_id)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Unsubscribe from device events."""
|
||||
@ -352,13 +362,14 @@ class AbodeAutomation(Entity):
|
||||
self._event = event
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
"""Subscribe to a group of Abode timeline events."""
|
||||
if self._event:
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.add_event_callback,
|
||||
self._event,
|
||||
self._update_callback,
|
||||
)
|
||||
self.hass.data[DOMAIN].entity_ids.add(self.entity_id)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -23,9 +23,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up an alarm control panel for an Abode device."""
|
||||
"""Set up Abode alarm control panel device."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
async_add_entities(
|
||||
[AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))]
|
||||
)
|
||||
|
@ -5,9 +5,10 @@ import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import AbodeAutomation, AbodeDevice
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SIGNAL_TRIGGER_QUICK_ACTION
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -18,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
"""Set up Abode binary sensor devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
device_types = [
|
||||
@ -29,19 +30,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
CONST.TYPE_OPENING,
|
||||
]
|
||||
|
||||
devices = []
|
||||
entities = []
|
||||
|
||||
for device in data.abode.get_devices(generic_type=device_types):
|
||||
devices.append(AbodeBinarySensor(data, device))
|
||||
entities.append(AbodeBinarySensor(data, device))
|
||||
|
||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
|
||||
devices.append(
|
||||
entities.append(
|
||||
AbodeQuickActionBinarySensor(
|
||||
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
||||
@ -61,6 +62,12 @@ class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
||||
class AbodeQuickActionBinarySensor(AbodeAutomation, BinarySensorDevice):
|
||||
"""A binary sensor implementation for Abode quick action automations."""
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
await super().async_added_to_hass()
|
||||
signal = SIGNAL_TRIGGER_QUICK_ACTION.format(self.entity_id)
|
||||
async_dispatcher_connect(self.hass, signal, self.trigger)
|
||||
|
||||
def trigger(self):
|
||||
"""Trigger a quick automation."""
|
||||
self._automation.trigger()
|
||||
|
@ -7,10 +7,11 @@ import abodepy.helpers.timeline as TIMELINE
|
||||
import requests
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SIGNAL_CAPTURE_IMAGE
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
|
||||
@ -23,15 +24,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a camera for an Abode device."""
|
||||
|
||||
"""Set up Abode camera devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
|
||||
devices.append(AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE))
|
||||
entities = []
|
||||
|
||||
async_add_entities(devices)
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
|
||||
entities.append(AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeCamera(AbodeDevice, Camera):
|
||||
@ -54,6 +55,9 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
self._capture_callback,
|
||||
)
|
||||
|
||||
signal = SIGNAL_CAPTURE_IMAGE.format(self.entity_id)
|
||||
async_dispatcher_connect(self.hass, signal, self.capture)
|
||||
|
||||
def capture(self):
|
||||
"""Request a new image capture."""
|
||||
return self._device.capture()
|
||||
|
@ -3,3 +3,6 @@ DOMAIN = "abode"
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
DEFAULT_CACHEDB = "abodepy_cache.pickle"
|
||||
|
||||
SIGNAL_CAPTURE_IMAGE = "abode_camera_capture_{}"
|
||||
SIGNAL_TRIGGER_QUICK_ACTION = "abode_trigger_quick_action_{}"
|
||||
|
@ -18,14 +18,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode cover devices."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER):
|
||||
devices.append(AbodeCover(data, device))
|
||||
entities = []
|
||||
|
||||
async_add_entities(devices)
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER):
|
||||
entities.append(AbodeCover(data, device))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeCover(AbodeDevice, CoverDevice):
|
||||
|
@ -33,12 +33,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode light devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
entities = []
|
||||
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LIGHT):
|
||||
devices.append(AbodeLight(data, device))
|
||||
entities.append(AbodeLight(data, device))
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeLight(AbodeDevice, Light):
|
||||
|
@ -18,14 +18,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode lock devices."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK):
|
||||
devices.append(AbodeLock(data, device))
|
||||
entities = []
|
||||
|
||||
async_add_entities(devices)
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK):
|
||||
entities.append(AbodeLock(data, device))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeLock(AbodeDevice, LockDevice):
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": [
|
||||
"abodepy==0.16.6"
|
||||
"abodepy==0.16.7"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
@ -16,9 +16,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Sensor types: Name, icon
|
||||
SENSOR_TYPES = {
|
||||
"temp": ["Temperature", DEVICE_CLASS_TEMPERATURE],
|
||||
"humidity": ["Humidity", DEVICE_CLASS_HUMIDITY],
|
||||
"lux": ["Lux", DEVICE_CLASS_ILLUMINANCE],
|
||||
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],
|
||||
CONST.HUMI_STATUS_KEY: ["Humidity", DEVICE_CLASS_HUMIDITY],
|
||||
CONST.LUX_STATUS_KEY: ["Lux", DEVICE_CLASS_ILLUMINANCE],
|
||||
}
|
||||
|
||||
|
||||
@ -28,16 +28,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
|
||||
"""Set up Abode sensor devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
entities = []
|
||||
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
devices.append(AbodeSensor(data, device, sensor_type))
|
||||
if sensor_type not in device.get_value(CONST.STATUSES_KEY):
|
||||
continue
|
||||
entities.append(AbodeSensor(data, device, sensor_type))
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeSensor(AbodeDevice):
|
||||
@ -62,6 +64,11 @@ class AbodeSensor(AbodeDevice):
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this device."""
|
||||
return f"{self._device.device_uuid}-{self._sensor_type}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
|
@ -21,17 +21,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode switch devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
entities = []
|
||||
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SWITCH):
|
||||
devices.append(AbodeSwitch(data, device))
|
||||
entities.append(AbodeSwitch(data, device))
|
||||
|
||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION):
|
||||
devices.append(
|
||||
entities.append(
|
||||
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
||||
)
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AbodeSwitch(AbodeDevice, SwitchDevice):
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "\u0422\u0430\u0437\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0438\u0437\u0438\u0441\u043a\u0432\u0430 AdGuard Home {minimal_version} \u0438\u043b\u0438 \u043f\u043e-\u043d\u043e\u0432\u0430 {minimal_version}, \u0438\u043c\u0430\u0442\u0435 {current_version}. \u041c\u043e\u043b\u044f, \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u0439\u0442\u0435 \u0432\u0430\u0448\u0430\u0442\u0430 \u0434\u043e\u0431\u0430\u0432\u043a\u0430 \u0437\u0430 Hass.io AdGuard Home.",
|
||||
"adguard_home_outdated": "\u0422\u0430\u0437\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0438\u0437\u0438\u0441\u043a\u0432\u0430 AdGuard Home {minimal_version} \u0438\u043b\u0438 \u043f\u043e-\u043d\u043e\u0432\u0430 {minimal_version}, \u0438\u043c\u0430\u0442\u0435 {current_version}.",
|
||||
"existing_instance_updated": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f.",
|
||||
"single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo Hass.io AdGuard Home.",
|
||||
"adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.",
|
||||
"existing_instance_updated": "Configurazione esistente aggiornata.",
|
||||
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
|
||||
},
|
||||
|
22
homeassistant/components/airly/.translations/bg.json
Normal file
22
homeassistant/components/airly/.translations/bg.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"auth": "API \u043a\u043b\u044e\u0447\u044a\u0442 \u043d\u0435 \u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0435\u043d.",
|
||||
"name_exists": "\u0418\u043c\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430.",
|
||||
"wrong_location": "\u0412 \u0442\u0430\u0437\u0438 \u043e\u0431\u043b\u0430\u0441\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u043c\u0435\u0440\u0432\u0430\u0442\u0435\u043b\u043d\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Airly."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API \u043a\u043b\u044e\u0447 \u0437\u0430 Airly",
|
||||
"latitude": "\u0428\u0438\u0440\u0438\u043d\u0430",
|
||||
"longitude": "\u0414\u044a\u043b\u0436\u0438\u043d\u0430",
|
||||
"name": "\u0418\u043c\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430"
|
||||
},
|
||||
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0432\u044a\u0437\u0434\u0443\u0445\u0430 Airly \u0417\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 \u043a\u043b\u044e\u0447 \u0437\u0430 API, \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 https://developer.airly.eu/register",
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "\u0421\u043b\u043e\u0436\u0438 {entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c \u043e\u0442\u0441\u044a\u0441\u0442\u0432\u0438\u0435",
|
||||
"arm_home": "\u0421\u043b\u043e\u0436\u0438 {entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c \u0432\u043a\u044a\u0449\u0438",
|
||||
"arm_night": "\u0421\u043b\u043e\u0436\u0438 {entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 \u0432 \u043d\u043e\u0449\u0435\u043d \u0440\u0435\u0436\u0438\u043c",
|
||||
"disarm": "\u0414\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0439 {entity_name}",
|
||||
"trigger": "\u0417\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u0430\u043d\u0435 {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Aktivovat {entity_name} v re\u017eimu mimo domov",
|
||||
"arm_home": "Aktivovat {entity_name} v re\u017eimu doma",
|
||||
"arm_night": "Aktivovat {entity_name} v re\u017eimu noc",
|
||||
"disarm": "Deaktivovat {entity_name}",
|
||||
"trigger": "Spustit {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Inschakelen {entity_name} afwezig",
|
||||
"arm_home": "Inschakelen {entity_name} thuis",
|
||||
"arm_night": "Inschakelen {entity_name} nacht",
|
||||
"disarm": "Uitschakelen {entity_name}",
|
||||
"trigger": "Trigger {entity_name}"
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Armar {entity_name} longe",
|
||||
"arm_home": "Armar {entity_name} casa",
|
||||
"arm_night": "Armar {entity_name} noite",
|
||||
"disarm": "Desarmar {entity_name}",
|
||||
"trigger": "Disparar {entidade_nome}"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u0435 \u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
|
||||
"arm_home": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u0414\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
|
||||
"arm_night": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u043e\u0447\u044c\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
|
||||
"disarm": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0445\u0440\u0430\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
|
||||
"trigger": "{entity_name} \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
"""Reproduce an Alarm control panel state."""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
|
||||
SERVICE_ALARM_ARM_HOME,
|
||||
SERVICE_ALARM_ARM_NIGHT,
|
||||
SERVICE_ALARM_DISARM,
|
||||
SERVICE_ALARM_TRIGGER,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.core import Context, State
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALID_STATES = {
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
}
|
||||
|
||||
|
||||
async def _async_reproduce_state(
|
||||
hass: HomeAssistantType, state: State, context: Optional[Context] = None
|
||||
) -> None:
|
||||
"""Reproduce a single state."""
|
||||
cur_state = hass.states.get(state.entity_id)
|
||||
|
||||
if cur_state is None:
|
||||
_LOGGER.warning("Unable to find entity %s", state.entity_id)
|
||||
return
|
||||
|
||||
if state.state not in VALID_STATES:
|
||||
_LOGGER.warning(
|
||||
"Invalid state specified for %s: %s", state.entity_id, state.state
|
||||
)
|
||||
return
|
||||
|
||||
# Return if we are already at the right state.
|
||||
if cur_state.state == state.state:
|
||||
return
|
||||
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
|
||||
if state.state == STATE_ALARM_ARMED_AWAY:
|
||||
service = SERVICE_ALARM_ARM_AWAY
|
||||
elif state.state == STATE_ALARM_ARMED_CUSTOM_BYPASS:
|
||||
service = SERVICE_ALARM_ARM_CUSTOM_BYPASS
|
||||
elif state.state == STATE_ALARM_ARMED_HOME:
|
||||
service = SERVICE_ALARM_ARM_HOME
|
||||
elif state.state == STATE_ALARM_ARMED_NIGHT:
|
||||
service = SERVICE_ALARM_ARM_NIGHT
|
||||
elif state.state == STATE_ALARM_DISARMED:
|
||||
service = SERVICE_ALARM_DISARM
|
||||
elif state.state == STATE_ALARM_TRIGGERED:
|
||||
service = SERVICE_ALARM_TRIGGER
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, service, service_data, context=context, blocking=True
|
||||
)
|
||||
|
||||
|
||||
async def async_reproduce_states(
|
||||
hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None
|
||||
) -> None:
|
||||
"""Reproduce Alarm control panel states."""
|
||||
await asyncio.gather(
|
||||
*(_async_reproduce_state(hass, state, context) for state in states)
|
||||
)
|
@ -10,6 +10,16 @@ alarm_disarm:
|
||||
description: An optional code to disarm the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_custom_bypass:
|
||||
description: Send arm custom bypass command.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm custom bypass.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm custom bypass the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_home:
|
||||
description: Send the alarm the command for arm home.
|
||||
fields:
|
||||
|
@ -12,11 +12,14 @@ from homeassistant.const import (
|
||||
STATE_LOCKED,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_PAUSED,
|
||||
STATE_PLAYING,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNLOCKED,
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNLOCKED,
|
||||
)
|
||||
import homeassistant.components.climate.const as climate
|
||||
import homeassistant.components.media_player.const as media_player
|
||||
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
|
||||
from homeassistant.components import light, fan, cover
|
||||
import homeassistant.util.color as color_util
|
||||
@ -110,6 +113,11 @@ class AlexaCapability:
|
||||
"""Return the Configuration object."""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def supported_operations():
|
||||
"""Return the supportedOperations object."""
|
||||
return []
|
||||
|
||||
def serialize_discovery(self):
|
||||
"""Serialize according to the Discovery API."""
|
||||
result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"}
|
||||
@ -150,6 +158,10 @@ class AlexaCapability:
|
||||
if instance is not None:
|
||||
result["instance"] = instance
|
||||
|
||||
supported_operations = self.supported_operations()
|
||||
if supported_operations:
|
||||
result["supportedOperations"] = supported_operations
|
||||
|
||||
return result
|
||||
|
||||
def serialize_properties(self):
|
||||
@ -484,6 +496,28 @@ class AlexaPlaybackController(AlexaCapability):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
return "Alexa.PlaybackController"
|
||||
|
||||
def supported_operations(self):
|
||||
"""Return the supportedOperations object.
|
||||
|
||||
Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind, StartOver, Stop
|
||||
"""
|
||||
supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
operations = {
|
||||
media_player.SUPPORT_NEXT_TRACK: "Next",
|
||||
media_player.SUPPORT_PAUSE: "Pause",
|
||||
media_player.SUPPORT_PLAY: "Play",
|
||||
media_player.SUPPORT_PREVIOUS_TRACK: "Previous",
|
||||
media_player.SUPPORT_STOP: "Stop",
|
||||
}
|
||||
|
||||
supported_operations = []
|
||||
for operation in operations:
|
||||
if operation & supported_features:
|
||||
supported_operations.append(operations[operation])
|
||||
|
||||
return supported_operations
|
||||
|
||||
|
||||
class AlexaInputController(AlexaCapability):
|
||||
"""Implements Alexa.InputController.
|
||||
@ -704,6 +738,33 @@ class AlexaThermostatController(AlexaCapability):
|
||||
|
||||
return {"value": temp, "scale": API_TEMP_UNITS[unit]}
|
||||
|
||||
def configuration(self):
|
||||
"""Return configuration object.
|
||||
|
||||
Translates climate HVAC_MODES and PRESETS to supported Alexa ThermostatMode Values.
|
||||
ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM.
|
||||
"""
|
||||
supported_modes = []
|
||||
hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES)
|
||||
for mode in hvac_modes:
|
||||
thermostat_mode = API_THERMOSTAT_MODES.get(mode)
|
||||
if thermostat_mode:
|
||||
supported_modes.append(thermostat_mode)
|
||||
|
||||
preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES)
|
||||
for mode in preset_modes:
|
||||
thermostat_mode = API_THERMOSTAT_PRESETS.get(mode)
|
||||
if thermostat_mode:
|
||||
supported_modes.append(thermostat_mode)
|
||||
|
||||
# Return False for supportsScheduling until supported with event listener in handler.
|
||||
configuration = {"supportsScheduling": False}
|
||||
|
||||
if supported_modes:
|
||||
configuration["supportedModes"] = supported_modes
|
||||
|
||||
return configuration
|
||||
|
||||
|
||||
class AlexaPowerLevelController(AlexaCapability):
|
||||
"""Implements Alexa.PowerLevelController.
|
||||
@ -1078,3 +1139,50 @@ class AlexaDoorbellEventSource(AlexaCapability):
|
||||
def capability_proactively_reported(self):
|
||||
"""Return True for proactively reported capability."""
|
||||
return True
|
||||
|
||||
|
||||
class AlexaPlaybackStateReporter(AlexaCapability):
|
||||
"""Implements Alexa.PlaybackStateReporter.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-playbackstatereporter.html
|
||||
"""
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
return "Alexa.PlaybackStateReporter"
|
||||
|
||||
def properties_supported(self):
|
||||
"""Return what properties this entity supports."""
|
||||
return [{"name": "playbackState"}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return True
|
||||
|
||||
def properties_retrievable(self):
|
||||
"""Return True if properties can be retrieved."""
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
"""Read and return a property."""
|
||||
if name != "playbackState":
|
||||
raise UnsupportedProperty(name)
|
||||
|
||||
playback_state = self.entity.state
|
||||
if playback_state == STATE_PLAYING:
|
||||
return {"state": "PLAYING"}
|
||||
if playback_state == STATE_PAUSED:
|
||||
return {"state": "PAUSED"}
|
||||
|
||||
return {"state": "STOPPED"}
|
||||
|
||||
|
||||
class AlexaSeekController(AlexaCapability):
|
||||
"""Implements Alexa.SeekController.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-seekcontroller.html
|
||||
"""
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
return "Alexa.SeekController"
|
||||
|
@ -56,9 +56,10 @@ API_THERMOSTAT_MODES = OrderedDict(
|
||||
(climate.HVAC_MODE_AUTO, "AUTO"),
|
||||
(climate.HVAC_MODE_OFF, "OFF"),
|
||||
(climate.HVAC_MODE_FAN_ONLY, "OFF"),
|
||||
(climate.HVAC_MODE_DRY, "OFF"),
|
||||
(climate.HVAC_MODE_DRY, "CUSTOM"),
|
||||
]
|
||||
)
|
||||
API_THERMOSTAT_MODES_CUSTOM = {climate.HVAC_MODE_DRY: "DEHUMIDIFY"}
|
||||
API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"}
|
||||
|
||||
PERCENTAGE_FAN_MAP = {
|
||||
|
@ -46,11 +46,13 @@ from .capabilities import (
|
||||
AlexaMotionSensor,
|
||||
AlexaPercentageController,
|
||||
AlexaPlaybackController,
|
||||
AlexaPlaybackStateReporter,
|
||||
AlexaPowerController,
|
||||
AlexaPowerLevelController,
|
||||
AlexaRangeController,
|
||||
AlexaSceneController,
|
||||
AlexaSecurityPanelController,
|
||||
AlexaSeekController,
|
||||
AlexaSpeaker,
|
||||
AlexaStepSpeaker,
|
||||
AlexaTemperatureSensor,
|
||||
@ -391,6 +393,10 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
|
||||
if device_class == media_player.DEVICE_CLASS_SPEAKER:
|
||||
return [DisplayCategory.SPEAKER]
|
||||
|
||||
return [DisplayCategory.TV]
|
||||
|
||||
def interfaces(self):
|
||||
@ -418,6 +424,10 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||
)
|
||||
if supported & playback_features:
|
||||
yield AlexaPlaybackController(self.entity)
|
||||
yield AlexaPlaybackStateReporter(self.entity)
|
||||
|
||||
if supported & media_player.const.SUPPORT_SEEK:
|
||||
yield AlexaSeekController(self.entity)
|
||||
|
||||
if supported & media_player.SUPPORT_SELECT_SOURCE:
|
||||
yield AlexaInputController(self.entity)
|
||||
|
@ -111,3 +111,10 @@ class AlexaInvalidDirectiveError(AlexaError):
|
||||
|
||||
namespace = "Alexa"
|
||||
error_type = "INVALID_DIRECTIVE"
|
||||
|
||||
|
||||
class AlexaVideoActionNotPermittedForContentError(AlexaError):
|
||||
"""Class to represent action not permitted for content errors."""
|
||||
|
||||
namespace = "Alexa.Video"
|
||||
error_type = "ACTION_NOT_PERMITTED_FOR_CONTENT"
|
||||
|
@ -38,6 +38,7 @@ from homeassistant.util.temperature import convert as convert_temperature
|
||||
|
||||
from .const import (
|
||||
API_TEMP_UNITS,
|
||||
API_THERMOSTAT_MODES_CUSTOM,
|
||||
API_THERMOSTAT_MODES,
|
||||
API_THERMOSTAT_PRESETS,
|
||||
Cause,
|
||||
@ -53,6 +54,7 @@ from .errors import (
|
||||
AlexaSecurityPanelUnauthorizedError,
|
||||
AlexaTempRangeError,
|
||||
AlexaUnsupportedThermostatModeError,
|
||||
AlexaVideoActionNotPermittedForContentError,
|
||||
)
|
||||
from .state_report import async_enable_proactive_mode
|
||||
|
||||
@ -767,11 +769,28 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_PRESET_MODE
|
||||
data[climate.ATTR_PRESET_MODE] = climate.PRESET_ECO
|
||||
data[climate.ATTR_PRESET_MODE] = ha_preset
|
||||
|
||||
elif mode == "CUSTOM":
|
||||
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
|
||||
custom_mode = directive.payload["thermostatMode"]["customName"]
|
||||
custom_mode = next(
|
||||
(k for k, v in API_THERMOSTAT_MODES_CUSTOM.items() if v == custom_mode),
|
||||
None,
|
||||
)
|
||||
if custom_mode not in operation_list:
|
||||
msg = (
|
||||
f"The requested thermostat mode {mode}: {custom_mode} is not supported"
|
||||
)
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_HVAC_MODE
|
||||
data[climate.ATTR_HVAC_MODE] = custom_mode
|
||||
|
||||
else:
|
||||
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
|
||||
ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None)
|
||||
ha_modes = {k: v for k, v in API_THERMOSTAT_MODES.items() if v == mode}
|
||||
ha_mode = next(iter(set(ha_modes).intersection(operation_list)), None)
|
||||
if ha_mode not in operation_list:
|
||||
msg = f"The requested thermostat mode {mode} is not supported"
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
@ -1168,3 +1187,45 @@ async def async_api_skipchannel(hass, config, directive, context):
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@HANDLERS.register(("Alexa.SeekController", "AdjustSeekPosition"))
|
||||
async def async_api_seek(hass, config, directive, context):
|
||||
"""Process a seek request."""
|
||||
entity = directive.entity
|
||||
position_delta = int(directive.payload["deltaPositionMilliseconds"])
|
||||
|
||||
current_position = entity.attributes.get(media_player.ATTR_MEDIA_POSITION)
|
||||
if not current_position:
|
||||
msg = f"{entity} did not return the current media position."
|
||||
raise AlexaVideoActionNotPermittedForContentError(msg)
|
||||
|
||||
seek_position = int(current_position) + int(position_delta / 1000)
|
||||
|
||||
if seek_position < 0:
|
||||
seek_position = 0
|
||||
|
||||
media_duration = entity.attributes.get(media_player.ATTR_MEDIA_DURATION)
|
||||
if media_duration and 0 < int(media_duration) < seek_position:
|
||||
seek_position = media_duration
|
||||
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.ATTR_MEDIA_SEEK_POSITION: seek_position,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
media_player.DOMAIN,
|
||||
media_player.SERVICE_MEDIA_SEEK,
|
||||
data,
|
||||
blocking=False,
|
||||
context=context,
|
||||
)
|
||||
|
||||
# convert seconds to milliseconds for StateReport.
|
||||
seek_position = int(seek_position * 1000)
|
||||
|
||||
payload = {"properties": [{"name": "positionMilliseconds", "value": seek_position}]}
|
||||
return directive.response(
|
||||
name="StateReport", namespace="Alexa.SeekController", payload=payload
|
||||
)
|
||||
|
9
homeassistant/components/almond/.translations/bg.json
Normal file
9
homeassistant/components/almond/.translations/bg.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u0438\u043d Almond \u0430\u043a\u0430\u0443\u043d\u0442.",
|
||||
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
9
homeassistant/components/almond/.translations/ca.json
Normal file
9
homeassistant/components/almond/.translations/ca.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Nom\u00e9s pots configurar un \u00fanic compte amb Almond.",
|
||||
"cannot_connect": "No es pot connectar amb el servidor d'Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
9
homeassistant/components/almond/.translations/de.json
Normal file
9
homeassistant/components/almond/.translations/de.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Sie k\u00f6nnen nur ein Almond-Konto konfigurieren.",
|
||||
"cannot_connect": "Verbindung zum Almond-Server nicht m\u00f6glich."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
15
homeassistant/components/almond/.translations/en.json
Normal file
15
homeassistant/components/almond/.translations/en.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "You can only configure one Almond account.",
|
||||
"cannot_connect": "Unable to connect to the Almond server.",
|
||||
"missing_configuration": "Please check the documentation on how to set up Almond."
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "Pick Authentication Method"
|
||||
}
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
15
homeassistant/components/almond/.translations/es.json
Normal file
15
homeassistant/components/almond/.translations/es.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "S\u00f3lo puede configurar una cuenta de Almond.",
|
||||
"cannot_connect": "No se puede conectar al servidor Almond.",
|
||||
"missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond."
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "Seleccione el m\u00e9todo de autenticaci\u00f3n"
|
||||
}
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/fr.json
Normal file
10
homeassistant/components/almond/.translations/fr.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Vous ne pouvez configurer qu'un seul compte Almond",
|
||||
"cannot_connect": "Impossible de se connecter au serveur Almond",
|
||||
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/it.json
Normal file
10
homeassistant/components/almond/.translations/it.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "\u00c8 possibile configurare un solo account Almond.",
|
||||
"cannot_connect": "Impossibile connettersi al server Almond.",
|
||||
"missing_configuration": "Si prega di controllare la documentazione su come impostare Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
9
homeassistant/components/almond/.translations/ko.json
Normal file
9
homeassistant/components/almond/.translations/ko.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
|
||||
"cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/lb.json
Normal file
10
homeassistant/components/almond/.translations/lb.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Almond Kont konfigur\u00e9ieren.",
|
||||
"cannot_connect": "Kann sech net mam Almond Server verbannen.",
|
||||
"missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/nl.json
Normal file
10
homeassistant/components/almond/.translations/nl.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "U kunt slechts \u00e9\u00e9n Almond-account configureren.",
|
||||
"cannot_connect": "Kan geen verbinding maken met de Almond-server.",
|
||||
"missing_configuration": "Raadpleeg de documentatie over het instellen van Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/no.json
Normal file
10
homeassistant/components/almond/.translations/no.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Du kan bare konfigurere en Almond konto.",
|
||||
"cannot_connect": "Kan ikke koble til Almond-serveren.",
|
||||
"missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
9
homeassistant/components/almond/.translations/pl.json
Normal file
9
homeassistant/components/almond/.translations/pl.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Almond.",
|
||||
"cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z serwerem Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/ru.json
Normal file
10
homeassistant/components/almond/.translations/ru.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
|
||||
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Almond.",
|
||||
"missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/sl.json
Normal file
10
homeassistant/components/almond/.translations/sl.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "Konfigurirate lahko samo en ra\u010dun Almond.",
|
||||
"cannot_connect": "Ni mogo\u010de vzpostaviti povezave s stre\u017enikom Almond.",
|
||||
"missing_configuration": "Prosimo, preverite dokumentacijo o tem, kako nastaviti Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
10
homeassistant/components/almond/.translations/zh-Hant.json
Normal file
10
homeassistant/components/almond/.translations/zh-Hant.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Almond \u5e33\u865f\u3002",
|
||||
"cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Almond \u4f3a\u670d\u5668\u3002",
|
||||
"missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002"
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
308
homeassistant/components/almond/__init__.py
Normal file
308
homeassistant/components/almond/__init__.py
Normal file
@ -0,0 +1,308 @@
|
||||
"""Support for Almond."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import async_timeout
|
||||
from aiohttp import ClientSession, ClientError
|
||||
from pyalmond import AlmondLocalAuth, AbstractAlmondWebAuth, WebAlmondAPI
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant, CoreState
|
||||
from homeassistant.const import CONF_TYPE, CONF_HOST, EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
config_entry_oauth2_flow,
|
||||
event,
|
||||
intent,
|
||||
aiohttp_client,
|
||||
storage,
|
||||
network,
|
||||
)
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import conversation
|
||||
|
||||
from . import config_flow
|
||||
from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
|
||||
|
||||
CONF_CLIENT_ID = "client_id"
|
||||
CONF_CLIENT_SECRET = "client_secret"
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = DOMAIN
|
||||
|
||||
ALMOND_SETUP_DELAY = 30
|
||||
|
||||
DEFAULT_OAUTH2_HOST = "https://almond.stanford.edu"
|
||||
DEFAULT_LOCAL_HOST = "http://localhost:3000"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Any(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_TYPE): TYPE_OAUTH2,
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_OAUTH2_HOST): cv.url,
|
||||
}
|
||||
),
|
||||
vol.Schema(
|
||||
{vol.Required(CONF_TYPE): TYPE_LOCAL, vol.Required(CONF_HOST): cv.url}
|
||||
),
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Almond component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
host = conf[CONF_HOST]
|
||||
|
||||
if conf[CONF_TYPE] == TYPE_OAUTH2:
|
||||
config_flow.AlmondFlowHandler.async_register_implementation(
|
||||
hass,
|
||||
config_entry_oauth2_flow.LocalOAuth2Implementation(
|
||||
hass,
|
||||
DOMAIN,
|
||||
conf[CONF_CLIENT_ID],
|
||||
conf[CONF_CLIENT_SECRET],
|
||||
f"{host}/me/api/oauth2/authorize",
|
||||
f"{host}/me/api/oauth2/token",
|
||||
),
|
||||
)
|
||||
return True
|
||||
|
||||
if not hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={"type": TYPE_LOCAL, "host": conf[CONF_HOST]},
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry):
|
||||
"""Set up Almond config entry."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
if entry.data["type"] == TYPE_LOCAL:
|
||||
auth = AlmondLocalAuth(entry.data["host"], websession)
|
||||
else:
|
||||
# OAuth2
|
||||
implementation = await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
)
|
||||
oauth_session = config_entry_oauth2_flow.OAuth2Session(
|
||||
hass, entry, implementation
|
||||
)
|
||||
auth = AlmondOAuth(entry.data["host"], websession, oauth_session)
|
||||
|
||||
api = WebAlmondAPI(auth)
|
||||
agent = AlmondAgent(hass, api, entry)
|
||||
|
||||
# Hass.io does its own configuration.
|
||||
if not entry.data.get("is_hassio"):
|
||||
# If we're not starting or local, set up Almond right away
|
||||
if hass.state != CoreState.not_running or entry.data["type"] == TYPE_LOCAL:
|
||||
await _configure_almond_for_ha(hass, entry, api)
|
||||
|
||||
else:
|
||||
# OAuth2 implementations can potentially rely on the HA Cloud url.
|
||||
# This url is not be available until 30 seconds after boot.
|
||||
|
||||
async def configure_almond(_now):
|
||||
try:
|
||||
await _configure_almond_for_ha(hass, entry, api)
|
||||
except ConfigEntryNotReady:
|
||||
_LOGGER.warning(
|
||||
"Unable to configure Almond to connect to Home Assistant"
|
||||
)
|
||||
|
||||
async def almond_hass_start(_event):
|
||||
event.async_call_later(hass, ALMOND_SETUP_DELAY, configure_almond)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, almond_hass_start)
|
||||
|
||||
conversation.async_set_agent(hass, agent)
|
||||
return True
|
||||
|
||||
|
||||
async def _configure_almond_for_ha(
|
||||
hass: HomeAssistant, entry: config_entries.ConfigEntry, api: WebAlmondAPI
|
||||
):
|
||||
"""Configure Almond to connect to HA."""
|
||||
|
||||
if entry.data["type"] == TYPE_OAUTH2:
|
||||
# If we're connecting over OAuth2, we will only set up connection
|
||||
# with Home Assistant if we're remotely accessible.
|
||||
hass_url = network.async_get_external_url(hass)
|
||||
else:
|
||||
hass_url = hass.config.api.base_url
|
||||
|
||||
# If hass_url is None, we're not going to configure Almond to connect to HA.
|
||||
if hass_url is None:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
|
||||
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
data = await store.async_load()
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
user = None
|
||||
if "almond_user" in data:
|
||||
user = await hass.auth.async_get_user(data["almond_user"])
|
||||
|
||||
if user is None:
|
||||
user = await hass.auth.async_create_system_user("Almond", [GROUP_ID_ADMIN])
|
||||
data["almond_user"] = user.id
|
||||
await store.async_save(data)
|
||||
|
||||
refresh_token = await hass.auth.async_create_refresh_token(
|
||||
user,
|
||||
# Almond will be fine as long as we restart once every 5 years
|
||||
access_token_expiration=timedelta(days=365 * 5),
|
||||
)
|
||||
|
||||
# Create long lived access token
|
||||
access_token = hass.auth.async_create_access_token(refresh_token)
|
||||
|
||||
# Store token in Almond
|
||||
try:
|
||||
with async_timeout.timeout(30):
|
||||
await api.async_create_device(
|
||||
{
|
||||
"kind": "io.home-assistant",
|
||||
"hassUrl": hass_url,
|
||||
"accessToken": access_token,
|
||||
"refreshToken": "",
|
||||
# 5 years from now in ms.
|
||||
"accessTokenExpires": (time.time() + 60 * 60 * 24 * 365 * 5) * 1000,
|
||||
}
|
||||
)
|
||||
except (asyncio.TimeoutError, ClientError) as err:
|
||||
if isinstance(err, asyncio.TimeoutError):
|
||||
msg = "Request timeout"
|
||||
else:
|
||||
msg = err
|
||||
_LOGGER.warning("Unable to configure Almond: %s", msg)
|
||||
await hass.auth.async_remove_refresh_token(refresh_token)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
# Clear all other refresh tokens
|
||||
for token in list(user.refresh_tokens.values()):
|
||||
if token.id != refresh_token.id:
|
||||
await hass.auth.async_remove_refresh_token(token)
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
"""Unload Almond."""
|
||||
conversation.async_set_agent(hass, None)
|
||||
return True
|
||||
|
||||
|
||||
class AlmondOAuth(AbstractAlmondWebAuth):
|
||||
"""Almond Authentication using OAuth2."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
websession: ClientSession,
|
||||
oauth_session: config_entry_oauth2_flow.OAuth2Session,
|
||||
):
|
||||
"""Initialize Almond auth."""
|
||||
super().__init__(host, websession)
|
||||
self._oauth_session = oauth_session
|
||||
|
||||
async def async_get_access_token(self):
|
||||
"""Return a valid access token."""
|
||||
if not self._oauth_session.valid_token:
|
||||
await self._oauth_session.async_ensure_token_valid()
|
||||
|
||||
return self._oauth_session.token["access_token"]
|
||||
|
||||
|
||||
class AlmondAgent(conversation.AbstractConversationAgent):
|
||||
"""Almond conversation agent."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: WebAlmondAPI, entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Initialize the agent."""
|
||||
self.hass = hass
|
||||
self.api = api
|
||||
self.entry = entry
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return {"name": "Powered by Almond", "url": "https://almond.stanford.edu/"}
|
||||
|
||||
async def async_get_onboarding(self):
|
||||
"""Get onboard url if not onboarded."""
|
||||
if self.entry.data.get("onboarded"):
|
||||
return None
|
||||
|
||||
host = self.entry.data["host"]
|
||||
if self.entry.data.get("is_hassio"):
|
||||
host = "/core_almond"
|
||||
return {
|
||||
"text": "Would you like to opt-in to share your anonymized commands with Stanford to improve Almond's responses?",
|
||||
"url": f"{host}/conversation",
|
||||
}
|
||||
|
||||
async def async_set_onboarding(self, shown):
|
||||
"""Set onboarding status."""
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.entry, data={**self.entry.data, "onboarded": shown}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def async_process(
|
||||
self, text: str, conversation_id: Optional[str] = None
|
||||
) -> intent.IntentResponse:
|
||||
"""Process a sentence."""
|
||||
response = await self.api.async_converse_text(text, conversation_id)
|
||||
|
||||
first_choice = True
|
||||
buffer = ""
|
||||
for message in response["messages"]:
|
||||
if message["type"] == "text":
|
||||
buffer += "\n" + message["text"]
|
||||
elif message["type"] == "picture":
|
||||
buffer += "\n Picture: " + message["url"]
|
||||
elif message["type"] == "rdl":
|
||||
buffer += (
|
||||
"\n Link: "
|
||||
+ message["rdl"]["displayTitle"]
|
||||
+ " "
|
||||
+ message["rdl"]["webCallback"]
|
||||
)
|
||||
elif message["type"] == "choice":
|
||||
if first_choice:
|
||||
first_choice = False
|
||||
else:
|
||||
buffer += ","
|
||||
buffer += f" {message['title']}"
|
||||
|
||||
intent_result = intent.IntentResponse()
|
||||
intent_result.async_set_speech(buffer.strip())
|
||||
return intent_result
|
125
homeassistant/components/almond/config_flow.py
Normal file
125
homeassistant/components/almond/config_flow.py
Normal file
@ -0,0 +1,125 @@
|
||||
"""Config flow to connect with Home Assistant."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
from aiohttp import ClientError
|
||||
from yarl import URL
|
||||
import voluptuous as vol
|
||||
from pyalmond import AlmondLocalAuth, WebAlmondAPI
|
||||
|
||||
from homeassistant import data_entry_flow, config_entries, core
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, aiohttp_client
|
||||
|
||||
from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
|
||||
|
||||
|
||||
async def async_verify_local_connection(hass: core.HomeAssistant, host: str):
|
||||
"""Verify that a local connection works."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
api = WebAlmondAPI(AlmondLocalAuth(host, websession))
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(10):
|
||||
await api.async_list_apps()
|
||||
|
||||
return True
|
||||
except (asyncio.TimeoutError, ClientError):
|
||||
return False
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
|
||||
"""Implementation of the Almond OAuth2 config flow."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
host = None
|
||||
hassio_discovery = None
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
@property
|
||||
def extra_authorize_data(self) -> dict:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
return {"scope": "profile user-read user-read-results user-exec-command"}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
# Only allow 1 instance.
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="already_setup")
|
||||
|
||||
return await super().async_step_user(user_input)
|
||||
|
||||
async def async_step_auth(self, user_input=None):
|
||||
"""Handle authorize step."""
|
||||
result = await super().async_step_auth(user_input)
|
||||
|
||||
if result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP:
|
||||
self.host = str(URL(result["url"]).with_path("me"))
|
||||
|
||||
return result
|
||||
|
||||
async def async_oauth_create_entry(self, data: dict) -> dict:
|
||||
"""Create an entry for the flow.
|
||||
|
||||
Ok to override if you want to fetch extra info or even add another step.
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
self.CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
data["type"] = TYPE_OAUTH2
|
||||
data["host"] = self.host
|
||||
return self.async_create_entry(title=self.flow_impl.name, data=data)
|
||||
|
||||
async def async_step_import(self, user_input: dict = None) -> dict:
|
||||
"""Import data."""
|
||||
# Only allow 1 instance.
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="already_setup")
|
||||
|
||||
if not await async_verify_local_connection(self.hass, user_input["host"]):
|
||||
self.logger.warning(
|
||||
"Aborting import of Almond because we're unable to connect"
|
||||
)
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
self.CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
|
||||
return self.async_create_entry(
|
||||
title="Configuration.yaml",
|
||||
data={"type": TYPE_LOCAL, "host": user_input["host"]},
|
||||
)
|
||||
|
||||
async def async_step_hassio(self, user_input=None):
|
||||
"""Receive a Hass.io discovery."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="already_setup")
|
||||
|
||||
self.hassio_discovery = user_input
|
||||
|
||||
return await self.async_step_hassio_confirm()
|
||||
|
||||
async def async_step_hassio_confirm(self, user_input=None):
|
||||
"""Confirm a Hass.io discovery."""
|
||||
data = self.hassio_discovery
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=data["addon"],
|
||||
data={
|
||||
"is_hassio": True,
|
||||
"type": TYPE_LOCAL,
|
||||
"host": f"http://{data['host']}:{data['port']}",
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="hassio_confirm",
|
||||
description_placeholders={"addon": data["addon"]},
|
||||
data_schema=vol.Schema({}),
|
||||
)
|
4
homeassistant/components/almond/const.py
Normal file
4
homeassistant/components/almond/const.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""Constants for the Almond integration."""
|
||||
DOMAIN = "almond"
|
||||
TYPE_OAUTH2 = "oauth2"
|
||||
TYPE_LOCAL = "local"
|
9
homeassistant/components/almond/manifest.json
Normal file
9
homeassistant/components/almond/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "almond",
|
||||
"name": "Almond",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/almond",
|
||||
"dependencies": ["http", "conversation"],
|
||||
"codeowners": ["@gcampax", "@balloob"],
|
||||
"requirements": ["pyalmond==0.0.2"]
|
||||
}
|
15
homeassistant/components/almond/strings.json
Normal file
15
homeassistant/components/almond/strings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "Pick Authentication Method"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_setup": "You can only configure one Almond account.",
|
||||
"cannot_connect": "Unable to connect to the Almond server.",
|
||||
"missing_configuration": "Please check the documentation on how to set up Almond."
|
||||
},
|
||||
"title": "Almond"
|
||||
}
|
||||
}
|
@ -3,10 +3,10 @@
|
||||
"name": "Amazon polly",
|
||||
"documentation": "https://www.home-assistant.io/integrations/amazon_polly",
|
||||
"requirements": [
|
||||
"boto3==1.9.233"
|
||||
"boto3==1.9.252"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@robbiet480"
|
||||
]
|
||||
}
|
||||
}
|
@ -145,7 +145,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
def get_engine(hass, config):
|
||||
def get_engine(hass, config, discovery_info=None):
|
||||
"""Set up Amazon Polly speech component."""
|
||||
output_format = config.get(CONF_OUTPUT_FORMAT)
|
||||
sample_rate = config.get(CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATES[output_format])
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Androidtv",
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"requirements": [
|
||||
"adb-shell==0.0.7",
|
||||
"adb-shell==0.0.8",
|
||||
"androidtv==0.0.32"
|
||||
],
|
||||
"dependencies": [],
|
||||
|
@ -287,8 +287,11 @@ class ADBDevice(MediaPlayerDevice):
|
||||
"""Initialize the Android TV / Fire TV device."""
|
||||
self.aftv = aftv
|
||||
self._name = name
|
||||
self._apps = APPS.copy()
|
||||
self._apps.update(apps)
|
||||
self._app_id_to_name = APPS.copy()
|
||||
self._app_id_to_name.update(apps)
|
||||
self._app_name_to_id = {
|
||||
value: key for key, value in self._app_id_to_name.items()
|
||||
}
|
||||
self._keys = KEYS
|
||||
|
||||
self._device_properties = self.aftv.device_properties
|
||||
@ -328,7 +331,7 @@ class ADBDevice(MediaPlayerDevice):
|
||||
@property
|
||||
def app_name(self):
|
||||
"""Return the friendly name of the current app."""
|
||||
return self._apps.get(self._current_app, self._current_app)
|
||||
return self._app_id_to_name.get(self._current_app, self._current_app)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
@ -455,9 +458,13 @@ class AndroidTVDevice(ADBDevice):
|
||||
return
|
||||
|
||||
# Get the updated state and attributes.
|
||||
state, self._current_app, self._device, self._is_volume_muted, self._volume_level = (
|
||||
self.aftv.update()
|
||||
)
|
||||
(
|
||||
state,
|
||||
self._current_app,
|
||||
self._device,
|
||||
self._is_volume_muted,
|
||||
self._volume_level,
|
||||
) = self.aftv.update()
|
||||
|
||||
self._state = ANDROIDTV_STATES.get(state)
|
||||
if self._state is None:
|
||||
@ -514,7 +521,7 @@ class FireTVDevice(ADBDevice):
|
||||
super().__init__(aftv, name, apps, turn_on_command, turn_off_command)
|
||||
|
||||
self._get_sources = get_sources
|
||||
self._running_apps = None
|
||||
self._sources = None
|
||||
|
||||
@adb_decorator(override_available=True)
|
||||
def update(self):
|
||||
@ -534,23 +541,28 @@ class FireTVDevice(ADBDevice):
|
||||
return
|
||||
|
||||
# Get the `state`, `current_app`, and `running_apps`.
|
||||
state, self._current_app, self._running_apps = self.aftv.update(
|
||||
self._get_sources
|
||||
)
|
||||
state, self._current_app, running_apps = self.aftv.update(self._get_sources)
|
||||
|
||||
self._state = ANDROIDTV_STATES.get(state)
|
||||
if self._state is None:
|
||||
self._available = False
|
||||
|
||||
if running_apps:
|
||||
self._sources = [
|
||||
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
|
||||
]
|
||||
else:
|
||||
self._sources = None
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Return the current app."""
|
||||
return self._current_app
|
||||
return self._app_id_to_name.get(self._current_app, self._current_app)
|
||||
|
||||
@property
|
||||
def source_list(self):
|
||||
"""Return a list of running apps."""
|
||||
return self._running_apps
|
||||
return self._sources
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
@ -571,6 +583,7 @@ class FireTVDevice(ADBDevice):
|
||||
"""
|
||||
if isinstance(source, str):
|
||||
if not source.startswith("!"):
|
||||
self.aftv.launch_app(source)
|
||||
self.aftv.launch_app(self._app_name_to_id.get(source, source))
|
||||
else:
|
||||
self.aftv.stop_app(source[1:].lstrip())
|
||||
source_ = source[1:].lstrip()
|
||||
self.aftv.stop_app(self._app_name_to_id.get(source_, source_))
|
||||
|
@ -0,0 +1,4 @@
|
||||
# Describes the format for available arlo services
|
||||
|
||||
update:
|
||||
description: Update the state for all cameras and the base station.
|
@ -3,7 +3,7 @@
|
||||
"name": "Asuswrt",
|
||||
"documentation": "https://www.home-assistant.io/integrations/asuswrt",
|
||||
"requirements": [
|
||||
"aioasuswrt==1.1.21"
|
||||
"aioasuswrt==1.1.22"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
@ -25,7 +25,7 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
|
||||
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
|
||||
"title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
|
||||
}
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import exceptions
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import CALLBACK_TYPE, callback
|
||||
from homeassistant.const import (
|
||||
CONF_VALUE_TEMPLATE,
|
||||
CONF_PLATFORM,
|
||||
@ -17,7 +17,8 @@ from homeassistant.helpers.event import async_track_state_change, async_track_sa
|
||||
from homeassistant.helpers import condition, config_validation as cv, template
|
||||
|
||||
|
||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
|
||||
# mypy: no-check-untyped-defs
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
@ -42,7 +43,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_attach_trigger(
|
||||
hass, config, action, automation_info, *, platform_type="numeric_state"
|
||||
):
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
below = config.get(CONF_BELOW)
|
||||
@ -52,7 +53,7 @@ async def async_attach_trigger(
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
unsub_track_same = {}
|
||||
entities_triggered = set()
|
||||
period = {}
|
||||
period: dict = {}
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
@ -27,8 +27,8 @@ TRIGGER_SCHEMA = vol.All(
|
||||
vol.Required(CONF_PLATFORM): "state",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
|
||||
# These are str on purpose. Want to catch YAML conversions
|
||||
vol.Optional(CONF_FROM): str,
|
||||
vol.Optional(CONF_TO): str,
|
||||
vol.Optional(CONF_FROM): vol.Any(str, [str]),
|
||||
vol.Optional(CONF_TO): vol.Any(str, [str]),
|
||||
vol.Optional(CONF_FOR): vol.Any(
|
||||
vol.All(cv.time_period, cv.positive_timedelta),
|
||||
cv.template,
|
||||
|
@ -28,7 +28,9 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def async_attach_trigger(hass, config, action, automation_info):
|
||||
async def async_attach_trigger(
|
||||
hass, config, action, automation_info, *, platform_type="numeric_state"
|
||||
):
|
||||
"""Listen for state changes based on configuration."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
@ -65,7 +67,7 @@ async def async_attach_trigger(hass, config, action, automation_info):
|
||||
|
||||
variables = {
|
||||
"trigger": {
|
||||
"platform": "template",
|
||||
"platform": platform_type,
|
||||
"entity_id": entity_id,
|
||||
"from_state": from_s,
|
||||
"to_state": to_s,
|
||||
|
@ -4,5 +4,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/avea",
|
||||
"dependencies": [],
|
||||
"codeowners": ["@pattyland"],
|
||||
"requirements": ["avea==1.2.8"]
|
||||
}
|
||||
"requirements": ["avea==1.4"]
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Aws",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aws",
|
||||
"requirements": [
|
||||
"aiobotocore==0.10.2"
|
||||
"aiobotocore==0.10.4"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
@ -12,6 +12,7 @@
|
||||
"device_unavailable": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0435 \u0435 \u043d\u0430\u043b\u0438\u0447\u043d\u043e",
|
||||
"faulty_credentials": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438"
|
||||
},
|
||||
"flow_title": "Axis \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e: {name} ({host})",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
|
5
homeassistant/components/axis/.translations/cs.json
Normal file
5
homeassistant/components/axis/.translations/cs.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "Za\u0159\u00edzen\u00ed Axis: {name} ({host})"
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
"device_unavailable": "O dispositivo n\u00e3o est\u00e1 dispon\u00edvel",
|
||||
"faulty_credentials": "Credenciais do usu\u00e1rio inv\u00e1lidas"
|
||||
},
|
||||
"flow_title": "Eixos do dispositivo: {name} ({host})",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
|
1
homeassistant/components/azure_service_bus/__init__.py
Normal file
1
homeassistant/components/azure_service_bus/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""The Azure Service Bus integration."""
|
12
homeassistant/components/azure_service_bus/manifest.json
Normal file
12
homeassistant/components/azure_service_bus/manifest.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"domain": "azure_service_bus",
|
||||
"name": "Azure Service Bus",
|
||||
"documentation": "https://www.home-assistant.io/integrations/azure_service_bus",
|
||||
"requirements": [
|
||||
"azure-servicebus==0.50.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@hfurubotten"
|
||||
]
|
||||
}
|
106
homeassistant/components/azure_service_bus/notify.py
Normal file
106
homeassistant/components/azure_service_bus/notify.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Support for azure service bus notification."""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from azure.servicebus.aio import Message, ServiceBusClient
|
||||
from azure.servicebus.common.errors import (
|
||||
MessageSendFailed,
|
||||
ServiceBusConnectionError,
|
||||
ServiceBusResourceNotFound,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_DATA,
|
||||
ATTR_TARGET,
|
||||
ATTR_TITLE,
|
||||
PLATFORM_SCHEMA,
|
||||
BaseNotificationService,
|
||||
)
|
||||
from homeassistant.const import CONTENT_TYPE_JSON
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
CONF_CONNECTION_STRING = "connection_string"
|
||||
CONF_QUEUE_NAME = "queue"
|
||||
CONF_TOPIC_NAME = "topic"
|
||||
|
||||
ATTR_ASB_MESSAGE = "message"
|
||||
ATTR_ASB_TITLE = "title"
|
||||
ATTR_ASB_TARGET = "target"
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.has_at_least_one_key(CONF_QUEUE_NAME, CONF_TOPIC_NAME),
|
||||
PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_CONNECTION_STRING): cv.string,
|
||||
vol.Exclusive(
|
||||
CONF_QUEUE_NAME, "output", "Can only send to a queue or a topic."
|
||||
): cv.string,
|
||||
vol.Exclusive(
|
||||
CONF_TOPIC_NAME, "output", "Can only send to a queue or a topic."
|
||||
): cv.string,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_service(hass, config, discovery_info=None):
|
||||
"""Get the notification service."""
|
||||
connection_string = config[CONF_CONNECTION_STRING]
|
||||
queue_name = config.get(CONF_QUEUE_NAME)
|
||||
topic_name = config.get(CONF_TOPIC_NAME)
|
||||
|
||||
# Library can do synchronous IO when creating the clients.
|
||||
# Passes in loop here, but can't run setup on the event loop.
|
||||
servicebus = ServiceBusClient.from_connection_string(
|
||||
connection_string, loop=hass.loop
|
||||
)
|
||||
|
||||
try:
|
||||
if queue_name:
|
||||
client = servicebus.get_queue(queue_name)
|
||||
else:
|
||||
client = servicebus.get_topic(topic_name)
|
||||
except (ServiceBusConnectionError, ServiceBusResourceNotFound) as err:
|
||||
_LOGGER.error(
|
||||
"Connection error while creating client for queue/topic '%s'. %s",
|
||||
queue_name or topic_name,
|
||||
err,
|
||||
)
|
||||
return None
|
||||
|
||||
return ServiceBusNotificationService(client)
|
||||
|
||||
|
||||
class ServiceBusNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for the service bus service."""
|
||||
|
||||
def __init__(self, client):
|
||||
"""Initialize the service."""
|
||||
self._client = client
|
||||
|
||||
async def async_send_message(self, message, **kwargs):
|
||||
"""Send a message."""
|
||||
dto = {ATTR_ASB_MESSAGE: message}
|
||||
|
||||
if ATTR_TITLE in kwargs:
|
||||
dto[ATTR_ASB_TITLE] = kwargs[ATTR_TITLE]
|
||||
if ATTR_TARGET in kwargs:
|
||||
dto[ATTR_ASB_TARGET] = kwargs[ATTR_TARGET]
|
||||
|
||||
data = kwargs.get(ATTR_DATA)
|
||||
if data:
|
||||
dto.update(data)
|
||||
|
||||
queue_message = Message(json.dumps(dto))
|
||||
queue_message.properties.content_type = CONTENT_TYPE_JSON
|
||||
try:
|
||||
await self._client.send(queue_message)
|
||||
except MessageSendFailed as err:
|
||||
_LOGGER.error(
|
||||
"Could not send service bus notification to %s. %s",
|
||||
self._client.name,
|
||||
err,
|
||||
)
|
@ -52,7 +52,7 @@ _OPTIONS = {
|
||||
SUPPORTED_OPTIONS = [CONF_PERSON, CONF_PITCH, CONF_SPEED, CONF_VOLUME]
|
||||
|
||||
|
||||
def get_engine(hass, config):
|
||||
def get_engine(hass, config, discovery_info=None):
|
||||
"""Set up Baidu TTS component."""
|
||||
return BaiduTTSProvider(hass, config)
|
||||
|
||||
|
@ -53,6 +53,7 @@
|
||||
"hot": "{entity_name} \u0441\u0435 \u0441\u0442\u043e\u043f\u043b\u0438",
|
||||
"light": "{entity_name} \u0437\u0430\u043f\u043e\u0447\u043d\u0430 \u0434\u0430 \u043e\u0442\u043a\u0440\u0438\u0432\u0430 \u0441\u0432\u0435\u0442\u043b\u0438\u043d\u0430",
|
||||
"locked": "{entity_name} \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"moist": "{entity_name} \u0441\u0442\u0430\u043d\u0430 \u0432\u043b\u0430\u0436\u0435\u043d",
|
||||
"moist\u00a7": "{entity_name} \u0441\u0442\u0430\u0432\u0430 \u0432\u043b\u0430\u0436\u0435\u043d",
|
||||
"motion": "{entity_name} \u0437\u0430\u043f\u043e\u0447\u043d\u0430 \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435",
|
||||
"moving": "{entity_name} \u0437\u0430\u043f\u043e\u0447\u043d\u0430 \u043e\u0442\u043a\u0440\u0438\u0432\u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435",
|
||||
@ -71,6 +72,7 @@
|
||||
"not_moist": "{entity_name} \u0441\u0442\u0430\u0432\u0430 \u0441\u0443\u0445",
|
||||
"not_moving": "{entity_name} \u0441\u043f\u0440\u044f \u0434\u0430 \u0441\u0435 \u0434\u0432\u0438\u0436\u0438",
|
||||
"not_occupied": "{entity_name} \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u0437\u0430\u0435\u0442",
|
||||
"not_opened": "{entity_name} \u0437\u0430\u0442\u0432\u043e\u0440\u0435\u043d",
|
||||
"not_plugged_in": "{entity_name} \u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"not_powered": "{entity_name} \u043d\u0435 \u0441\u0435 \u0437\u0430\u0445\u0440\u0430\u043d\u0432\u0430",
|
||||
"not_present": "{entity_name} \u043d\u0435 \u043f\u0440\u0438\u0441\u044a\u0441\u0442\u0432\u0430",
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"moist": "{entity_name} se navlh\u010dil",
|
||||
"not_opened": "{entity_name} uzav\u0159eno"
|
||||
}
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@
|
||||
"smoke": "{entity_name} commenc\u00e9 \u00e0 d\u00e9tecter la fum\u00e9e",
|
||||
"sound": "{entity_name} commenc\u00e9 \u00e0 d\u00e9tecter le son",
|
||||
"turned_off": "{entity_name} est d\u00e9sactiv\u00e9",
|
||||
"turned_on": "{entity_name} activ\u00e9",
|
||||
"turned_on": "{entity_name} est activ\u00e9",
|
||||
"unsafe": "{entity_name} est devenu dangereux",
|
||||
"vibration": "{entity_name} a commenc\u00e9 \u00e0 d\u00e9tecter les vibrations"
|
||||
}
|
||||
|
@ -46,13 +46,14 @@
|
||||
},
|
||||
"trigger_type": {
|
||||
"bat_low": "{entity_name} akkufesz\u00fclts\u00e9g alacsony",
|
||||
"closed": "{entity_name} be lett z\u00e1rva",
|
||||
"closed": "{entity_name} be lett csukva",
|
||||
"cold": "{entity_name} hideg lett",
|
||||
"connected": "{entity_name} csatlakozott",
|
||||
"connected": "{entity_name} csatlakozik",
|
||||
"gas": "{entity_name} g\u00e1zt \u00e9rz\u00e9kel",
|
||||
"hot": "{entity_name} felforr\u00f3sodott",
|
||||
"hot": "{entity_name} felforr\u00f3sodik",
|
||||
"light": "{entity_name} f\u00e9nyt \u00e9rz\u00e9kel",
|
||||
"locked": "{entity_name} be lett z\u00e1rva",
|
||||
"moist": "{entity_name} nedves lett",
|
||||
"moist\u00a7": "{entity_name} nedves lett",
|
||||
"motion": "{entity_name} mozg\u00e1st \u00e9rz\u00e9kel",
|
||||
"moving": "{entity_name} mozog",
|
||||
@ -65,12 +66,13 @@
|
||||
"no_vibration": "{entity_name} m\u00e1r nem \u00e9rz\u00e9kel rezg\u00e9st",
|
||||
"not_bat_low": "{entity_name} akkufesz\u00fclts\u00e9g megfelel\u0151",
|
||||
"not_cold": "{entity_name} m\u00e1r nem hideg",
|
||||
"not_connected": "{entity_name} lecsatlakozott",
|
||||
"not_connected": "{entity_name} lecsatlakozik",
|
||||
"not_hot": "{entity_name} m\u00e1r nem forr\u00f3",
|
||||
"not_locked": "{entity_name} ki lett nyitva",
|
||||
"not_moist": "{entity_name} sz\u00e1raz lett",
|
||||
"not_moving": "{entity_name} m\u00e1r nem mozog",
|
||||
"not_occupied": "{entity_name} m\u00e1r nem foglalt",
|
||||
"not_opened": "{entity_name} be lett csukva",
|
||||
"not_plugged_in": "{entity_name} m\u00e1r nincs csatlakoztatva",
|
||||
"not_powered": "{entity_name} m\u00e1r nincs fesz\u00fcts\u00e9g alatt",
|
||||
"not_present": "{entity_name} m\u00e1r nincs jelen",
|
||||
|
@ -28,7 +28,7 @@
|
||||
"is_not_occupied": "{entity_name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435",
|
||||
"is_not_open": "{entity_name} \u0432 \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438",
|
||||
"is_not_plugged_in": "{entity_name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435",
|
||||
"is_not_powered": "{entity_name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043d\u0435\u0440\u0433\u0438\u044e",
|
||||
"is_not_powered": "{entity_name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0438\u0442\u0430\u043d\u0438\u0435",
|
||||
"is_not_present": "{entity_name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435",
|
||||
"is_not_unsafe": "{entity_name} \u0432 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438",
|
||||
"is_occupied": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435",
|
||||
@ -36,7 +36,7 @@
|
||||
"is_on": "{entity_name} \u0432\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438",
|
||||
"is_open": "{entity_name} \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438",
|
||||
"is_plugged_in": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435",
|
||||
"is_powered": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043d\u0435\u0440\u0433\u0438\u044e",
|
||||
"is_powered": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0438\u0442\u0430\u043d\u0438\u0435",
|
||||
"is_present": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435",
|
||||
"is_problem": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443",
|
||||
"is_smoke": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0434\u044b\u043c",
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Support for Bluesound devices."""
|
||||
import asyncio
|
||||
from asyncio.futures import CancelledError
|
||||
from asyncio import CancelledError
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from urllib import parse
|
||||
@ -53,6 +53,7 @@ import homeassistant.util.dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_BLUESOUND_GROUP = "bluesound_group"
|
||||
ATTR_MASTER = "master"
|
||||
|
||||
DATA_BLUESOUND = "bluesound"
|
||||
@ -219,6 +220,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
self._master = None
|
||||
self._is_master = False
|
||||
self._group_name = None
|
||||
self._group_list = []
|
||||
self._bluesound_device_name = None
|
||||
|
||||
self._init_callback = init_callback
|
||||
if self.port is None:
|
||||
@ -247,6 +250,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
|
||||
if not self._name:
|
||||
self._name = self._sync_status.get("@name", self.host)
|
||||
if not self._bluesound_device_name:
|
||||
self._bluesound_device_name = self._sync_status.get("@name", self.host)
|
||||
if not self._icon:
|
||||
self._icon = self._sync_status.get("@icon", self.host)
|
||||
|
||||
@ -331,7 +336,6 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
self, method, raise_timeout=False, allow_offline=False
|
||||
):
|
||||
"""Send command to the player."""
|
||||
|
||||
if not self._is_online and not allow_offline:
|
||||
return
|
||||
|
||||
@ -371,7 +375,6 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
|
||||
async def async_update_status(self):
|
||||
"""Use the poll session to always get the status of the player."""
|
||||
|
||||
response = None
|
||||
|
||||
url = "Status"
|
||||
@ -402,6 +405,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
if group_name != self._group_name:
|
||||
_LOGGER.debug("Group name change detected on device: %s", self.host)
|
||||
self._group_name = group_name
|
||||
|
||||
# rebuild ordered list of entity_ids that are in the group, master is first
|
||||
self._group_list = self.rebuild_bluesound_group()
|
||||
|
||||
# the sleep is needed to make sure that the
|
||||
# devices is synced
|
||||
await asyncio.sleep(1)
|
||||
@ -659,6 +666,11 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def bluesound_device_name(self):
|
||||
"""Return the device name as returned by the device."""
|
||||
return self._bluesound_device_name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of the device."""
|
||||
@ -690,7 +702,6 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
@property
|
||||
def source(self):
|
||||
"""Name of the current input source."""
|
||||
|
||||
if self._status is None or (self.is_grouped and not self.is_master):
|
||||
return None
|
||||
|
||||
@ -831,6 +842,39 @@ class BluesoundPlayer(MediaPlayerDevice):
|
||||
else:
|
||||
_LOGGER.error("Master not found %s", master_device)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""List members in group."""
|
||||
attributes = {}
|
||||
if self._group_list:
|
||||
attributes = {ATTR_BLUESOUND_GROUP: self._group_list}
|
||||
|
||||
attributes[ATTR_MASTER] = self._is_master
|
||||
|
||||
return attributes
|
||||
|
||||
def rebuild_bluesound_group(self):
|
||||
"""Rebuild the list of entities in speaker group."""
|
||||
if self._group_name is None:
|
||||
return None
|
||||
|
||||
bluesound_group = []
|
||||
|
||||
device_group = self._group_name.split("+")
|
||||
|
||||
sorted_entities = sorted(
|
||||
self._hass.data[DATA_BLUESOUND],
|
||||
key=lambda entity: entity.is_master,
|
||||
reverse=True,
|
||||
)
|
||||
bluesound_group = [
|
||||
entity.name
|
||||
for entity in sorted_entities
|
||||
if entity.bluesound_device_name in device_group
|
||||
]
|
||||
|
||||
return bluesound_group
|
||||
|
||||
async def async_unjoin(self):
|
||||
"""Unjoin the player from a group."""
|
||||
if self._master is None:
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "BMW Connected Drive",
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"requirements": [
|
||||
"bimmer_connected==0.6.0"
|
||||
"bimmer_connected==0.6.2"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "\u0422\u0430\u0437\u0438 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u043e\u0442 \u0430\u0434\u0440\u0435\u0441 \u0438 \u043f\u043e\u0440\u0442 \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044a\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0430\u043d",
|
||||
"certificate_fetch_failed": "\u041d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043c\u0438\u0437\u0432\u043b\u0435\u0447\u0435 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u043e\u0442 \u0442\u0430\u0437\u0438 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u043e\u0442 \u0430\u0434\u0440\u0435\u0441 \u0438 \u043f\u043e\u0440\u0442",
|
||||
"connection_timeout": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0441\u0432\u043e\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0442\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441",
|
||||
"host_port_exists": "\u0422\u0430\u0437\u0438 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u043e\u0442 \u0430\u0434\u0440\u0435\u0441 \u0438 \u043f\u043e\u0440\u0442 \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430",
|
||||
"resolve_failed": "\u0422\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d"
|
||||
"resolve_failed": "\u0422\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d",
|
||||
"wrong_host": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044a\u0442 \u043d\u0435 \u0441\u044a\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0430 \u043d\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0445\u043e\u0441\u0442\u0430"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"certificate_error": "Certifik\u00e1t nelze ov\u011b\u0159it",
|
||||
"wrong_host": "Certifik\u00e1t neodpov\u00edd\u00e1 n\u00e1zvu hostitele"
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "Esta combinaci\u00f3n de host y puerto ya est\u00e1 configurada"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "El certificado no pudo ser validado",
|
||||
"certificate_fetch_failed": "No se puede obtener el certificado de esta combinaci\u00f3n de host y puerto",
|
||||
"connection_timeout": "Tiempo de espera agotado al conectar a este host",
|
||||
"host_port_exists": "Esta combinaci\u00f3n de host y puerto ya est\u00e1 configurada",
|
||||
"resolve_failed": "Este host no se puede resolver"
|
||||
"resolve_failed": "Este host no se puede resolver",
|
||||
"wrong_host": "El certificado no coincide con el nombre de host"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "Questa combinazione di host e porta \u00e8 gi\u00e0 configurata"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "Il certificato non pu\u00f2 essere convalidato",
|
||||
"certificate_fetch_failed": "Non \u00e8 possibile recuperare il certificato da questa combinazione di host e porta",
|
||||
"connection_timeout": "Tempo scaduto collegandosi a questo host",
|
||||
"host_port_exists": "Questa combinazione di host e porta \u00e8 gi\u00e0 configurata",
|
||||
"resolve_failed": "Questo host non pu\u00f2 essere risolto"
|
||||
"resolve_failed": "Questo host non pu\u00f2 essere risolto",
|
||||
"wrong_host": "Il certificato non corrisponde al nome host"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "\ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "\uc778\uc99d\uc11c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
|
||||
"certificate_fetch_failed": "\ud574\ub2f9 \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\uc5d0\uc11c \uc778\uc99d\uc11c\ub97c \uac00\uc838 \uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
|
||||
"connection_timeout": "\ud638\uc2a4\ud2b8 \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4",
|
||||
"host_port_exists": "\ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||
"resolve_failed": "\ud638\uc2a4\ud2b8\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"
|
||||
"resolve_failed": "\ud638\uc2a4\ud2b8\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
|
||||
"wrong_host": "\uc778\uc99d\uc11c\uac00 \ud638\uc2a4\ud2b8 \uc774\ub984\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "D\u00ebsen Host an Port sinn scho konfigur\u00e9iert"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "Zertifikat konnt net valid\u00e9iert ginn",
|
||||
"certificate_fetch_failed": "Kann keen Zertifikat vun d\u00ebsen Host a Port recuper\u00e9ieren",
|
||||
"connection_timeout": "Z\u00e4it Iwwerschreidung beim verbannen.",
|
||||
"host_port_exists": "D\u00ebsen Host an Port sinn scho konfigur\u00e9iert",
|
||||
"resolve_failed": "D\u00ebsen Host kann net opgel\u00e9ist ginn"
|
||||
"resolve_failed": "D\u00ebsen Host kann net opgel\u00e9ist ginn",
|
||||
"wrong_host": "Zertifikat entspr\u00e9cht net den Numm vum Apparat"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "Ta kombinacja hosta i portu jest ju\u017c skonfigurowana"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "Nie mo\u017cna zweryfikowa\u0107 certyfikatu",
|
||||
"certificate_fetch_failed": "Nie mo\u017cna pobra\u0107 certyfikatu z tej kombinacji hosta i portu",
|
||||
"connection_timeout": "Przekroczono limit czasu po\u0142\u0105czenia z tym hostem",
|
||||
"connection_timeout": "Przekroczono limit czasu po\u0142\u0105czenia z hostem.",
|
||||
"host_port_exists": "Ta kombinacja hosta i portu jest ju\u017c skonfigurowana",
|
||||
"resolve_failed": "Tego hosta nie mo\u017cna rozwi\u0105za\u0107"
|
||||
"resolve_failed": "Tego hosta nie mo\u017cna rozwi\u0105za\u0107",
|
||||
"wrong_host": "Certyfikat nie pasuje do nazwy hosta"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "Ta kombinacija gostitelja in vrat je \u017ee konfigurirana"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "Certifikata ni bilo mogo\u010de preveriti",
|
||||
"certificate_fetch_failed": "Iz te kombinacije gostitelja in vrat ni mogo\u010de pridobiti potrdila",
|
||||
"connection_timeout": "\u010casovna omejitev za povezavo s tem gostiteljem je potekla",
|
||||
"host_port_exists": "Ta kombinacija gostitelja in vrat je \u017ee konfigurirana",
|
||||
"resolve_failed": "Tega gostitelja ni mogo\u010de razre\u0161iti"
|
||||
"resolve_failed": "Tega gostitelja ni mogo\u010de razre\u0161iti",
|
||||
"wrong_host": "Potrdilo se ne ujema z imenom gostitelja"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@ -4,10 +4,12 @@
|
||||
"host_port_exists": "\u6b64\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u7d44\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
|
||||
},
|
||||
"error": {
|
||||
"certificate_error": "\u8a8d\u8b49\u7121\u6cd5\u78ba\u8a8d",
|
||||
"certificate_fetch_failed": "\u7121\u6cd5\u81ea\u6b64\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u7d44\u5408\u7372\u5f97\u8a8d\u8b49",
|
||||
"connection_timeout": "\u9023\u7dda\u81f3\u4e3b\u6a5f\u7aef\u903e\u6642",
|
||||
"host_port_exists": "\u6b64\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u7d44\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
|
||||
"resolve_failed": "\u4e3b\u6a5f\u7aef\u7121\u6cd5\u89e3\u6790"
|
||||
"resolve_failed": "\u4e3b\u6a5f\u7aef\u7121\u6cd5\u89e3\u6790",
|
||||
"wrong_host": "\u8a8d\u8b49\u8207\u4e3b\u6a5f\u540d\u7a31\u4e0d\u7b26\u5408"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
16
homeassistant/components/climate/.translations/ca.json
Normal file
16
homeassistant/components/climate/.translations/ca.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"set_hvac_mode": "Canvia el mode HVAC de {entity_name}"
|
||||
},
|
||||
"condtion_type": {
|
||||
"is_hvac_mode": "{entity_name} est\u00e0 configurat/ada en un mode HVAC espec\u00edfic",
|
||||
"is_preset_mode": "{entity_name} est\u00e0 configurat/ada en un mode preestablert espec\u00edfic"
|
||||
},
|
||||
"trigger_type": {
|
||||
"current_humidity_changed": "Ha canviat la humitat mesurada per {entity_name}",
|
||||
"current_temperature_changed": "Ha canviat la temperatura mesurada per {entity_name}",
|
||||
"hvac_mode_changed": "El mode HVAC de {entity_name} ha canviat"
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/climate/.translations/en.json
Normal file
17
homeassistant/components/climate/.translations/en.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"set_hvac_mode": "Change HVAC mode on {entity_name}",
|
||||
"set_preset_mode": "Change preset on {entity_name}"
|
||||
},
|
||||
"condtion_type": {
|
||||
"is_hvac_mode": "{entity_name} is set to a specific HVAC mode",
|
||||
"is_preset_mode": "{entity_name} is set to a specific preset mode"
|
||||
},
|
||||
"trigger_type": {
|
||||
"current_humidity_changed": "{entity_name} measured humidity changed",
|
||||
"current_temperature_changed": "{entity_name} measured temperature changed",
|
||||
"hvac_mode_changed": "{entity_name} HVAC mode changed"
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/climate/.translations/es.json
Normal file
17
homeassistant/components/climate/.translations/es.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"set_hvac_mode": "Cambiar el modo HVAC de {entity_name}.",
|
||||
"set_preset_mode": "Cambiar la configuraci\u00f3n prefijada de {entity_name}"
|
||||
},
|
||||
"condtion_type": {
|
||||
"is_hvac_mode": "{entity_name} est\u00e1 configurado en un modo HVAC espec\u00edfico",
|
||||
"is_preset_mode": "{entity_name} se establece en un modo predeterminado espec\u00edfico"
|
||||
},
|
||||
"trigger_type": {
|
||||
"current_humidity_changed": "{entity_name} humedad medida cambi\u00f3",
|
||||
"current_temperature_changed": "{entity_name} temperatura medida cambi\u00f3",
|
||||
"hvac_mode_changed": "{entity_name} Modo HVAC cambiado"
|
||||
}
|
||||
}
|
||||
}
|
12
homeassistant/components/climate/.translations/fr.json
Normal file
12
homeassistant/components/climate/.translations/fr.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"set_preset_mode": "Changer les pr\u00e9r\u00e9glages de {entity_name}"
|
||||
},
|
||||
"trigger_type": {
|
||||
"current_humidity_changed": "Changement d'humidit\u00e9 mesur\u00e9e pour {entity_name}",
|
||||
"current_temperature_changed": "Changement de temp\u00e9rature mesur\u00e9e pour {entity_name}",
|
||||
"hvac_mode_changed": "Mode HVAC chang\u00e9 pour {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/climate/.translations/it.json
Normal file
17
homeassistant/components/climate/.translations/it.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"set_hvac_mode": "Cambia modalit\u00e0 HVAC su {entity_name}",
|
||||
"set_preset_mode": "Modifica preimpostazione su {entity_name}"
|
||||
},
|
||||
"condtion_type": {
|
||||
"is_hvac_mode": "{entity_name} \u00e8 impostato su una modalit\u00e0 HVAC specifica",
|
||||
"is_preset_mode": "{entity_name} \u00e8 impostato su una modalit\u00e0 preimpostata specifica"
|
||||
},
|
||||
"trigger_type": {
|
||||
"current_humidity_changed": "{entity_name} umidit\u00e0 misurata modificata",
|
||||
"current_temperature_changed": "{entity_name} temperatura misurata cambiata",
|
||||
"hvac_mode_changed": "{entity_name} modalit\u00e0 HVAC modificata"
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user